[java] 이진 트리가 균형을 이루는 지 확인하는 방법은 무엇입니까?

그 학창 시절부터 오래되었습니다. 병원에서 IT 전문가로 취직했습니다. 지금 실제 프로그래밍을하기 위해 이동하려고합니다. 나는 지금 이진 트리에 대해 작업하고 있는데, 트리가 높이 균형을 이루는 지 결정하는 가장 좋은 방법이 무엇인지 궁금합니다.

나는 이것과 함께 무언가를 생각하고 있었다.

public boolean isBalanced(Node root){
    if(root==null){
        return true;  //tree is empty
    }
    else{
        int lh = root.left.height();
        int rh = root.right.height();
        if(lh - rh > 1 || rh - lh > 1){
            return false;
        }
    }
    return true;
}

이것이 좋은 구현입니까? 아니면 내가 뭔가를 놓치고 있습니까?



답변

다른 것을 검색하는 동안이 오래된 질문을 우연히 발견했습니다. 나는 당신이 완전한 답을 얻지 못했다는 것을 알았습니다.

이 문제를 해결하는 방법은 작성하려는 함수에 대한 사양을 작성하여 시작하는 것입니다.

사양 : 잘 구성된 이진 트리는 (1) 비어 있거나 (2) 왼쪽 및 오른쪽 자식이 높이 균형을 이루고 왼쪽 트리의 높이가 1 사이에있는 경우 “높이 균형”이라고합니다. 오른쪽 나무의 높이.

이제 사양이 준비되었으므로 코드를 작성하는 것은 간단합니다. 사양을 따르십시오.

IsHeightBalanced(tree)
    return (tree is empty) or
           (IsHeightBalanced(tree.left) and
            IsHeightBalanced(tree.right) and
            abs(Height(tree.left) - Height(tree.right)) <= 1)

선택한 프로그래밍 언어로 번역하는 것은 간단합니다.

보너스 연습 :이 순진한 코드 스케치는 높이를 계산할 때 나무를 너무 많이 횡단합니다. 더 효율적으로 만들 수 있습니까?

슈퍼 보너스 운동 : 나무가 엄청나게 불균형 하다고 가정하십시오 . 한쪽에는 백만 개의 노드가 있고 다른쪽에는 세 개의 노드가 있습니다. 이 알고리즘이 스택을 파괴하는 시나리오가 있습니까? 엄청나게 불균형 한 트리가 주어 졌을 때에도 스택이 날아 가지 않도록 구현을 수정할 수 있습니까?

최신 정보 : Donal Fellows는 자신의 답변에서 선택할 수있는 ‘균형’에 대한 다른 정의가 있다고 지적합니다. 예를 들어, “높이 균형”에 대한보다 엄격한 정의를 취하고 가장 가까운 빈 자식에 대한 경로 길이가 가장 먼 빈 자식 에 대한 경로 중 하나 내에 있어야합니다 . 내 정의는 그보다 덜 엄격하므로 더 많은 나무를 인정합니다.

하나는 내 정의보다 덜 엄격 할 수도 있습니다. 균형 잡힌 트리는 각 가지에서 빈 트리까지의 최대 경로 길이가 2, 3 또는 다른 상수만큼 차이가 나는 트리라고 말할 수 있습니다. 또는 최대 경로 길이가 최소 경로 길이의 일부 (예 : 1/2 또는 1/4)입니다.

보통은 정말 중요하지 않습니다. 트리 밸런싱 알고리즘의 요점은 한쪽에 백만 개의 노드가 있고 다른쪽에 세 개가있는 상황에 처하지 않도록하는 것입니다. Donal의 정의는 이론적으로는 괜찮지 만 실제로는 그 엄격함 수준을 충족하는 트리 균형 알고리즘을 만드는 것이 고통입니다. 성능 절감은 일반적으로 구현 비용을 정당화하지 않습니다. 실제로는 거의 차이가없는 균형 수준을 얻기 위해 불필요한 트리 재 배열을하는 데 많은 시간을 할애합니다. 이론적으로 완벽하게 균형 잡힌 나무에서 20 개만 걸릴 수 있는데 백만 개의 노드 불완전하게 균형이 잡힌 나무에서 가장 먼 잎에 도달하는 데 때때로 40 개의 가지가 필요한데 누가 신경 쓰겠습니까? 요점은 백만 달러가 걸리지 않는다는 것입니다. 최악의 경우 100 만에서 최악의 경우 40으로 줄이면 보통 충분합니다. 최적의 경우로 끝까지 갈 필요가 없습니다.


답변

균형은 정말 미묘한 속성입니다. 당신은 그것이 무엇인지 알고 있다고 생각하지만 잘못되기가 너무 쉽습니다. 특히 Eric Lippert의 (좋은) 대답조차도 꺼져 있습니다. 높이 의 개념이 충분하지 않기 때문 입니다. 나무의 최소 및 최대 높이의 개념이 필요합니다 (최소 높이는 뿌리에서 잎까지의 최소 단계 수이고 최대 값은 … 음, 그림을 얻습니다). 이를 고려하여 균형을 다음과 같이 정의 할 수 있습니다.

나뭇 가지의 최대 높이가 나뭇 가지 의 최소 ​​높이 보다 1 배 이하인 나무.

(이것은 실제로 분기 자체가 균형을 이루고 있음을 의미합니다. 최대 및 최소 모두에 대해 동일한 분기를 선택할 수 있습니다.)

이 속성을 확인하기 위해 필요한 것은 현재 깊이를 추적하는 간단한 트리 순회뿐입니다. 처음으로 역 추적하면 기준 깊이가 제공됩니다. 그 후 역 추적 할 때마다 새 깊이를 기준선과 비교합니다.

  • 기준선과 같으면 계속
  • 둘 이상 다르면 나무가 균형을 이루지 못합니다.
  • 일회성 인 경우 이제 균형 범위를 알고 있으며 모든 후속 깊이 (역 추적하려는 경우)는 첫 번째 또는 두 번째 값이어야합니다.

코드에서 :

class Tree {
    Tree left, right;
    static interface Observer {
        public void before();
        public void after();
        public boolean end();
    }
    static boolean traverse(Tree t, Observer o) {
        if (t == null) {
            return o.end();
        } else {
            o.before();
            try {
                if (traverse(left, o))
                    return traverse(right, o);
                return false;
            } finally {
                o.after();
            }
        }
    }
    boolean balanced() {
        final Integer[] heights = new Integer[2];
        return traverse(this, new Observer() {
            int h;
            public void before() { h++; }
            public void after() { h--; }
            public boolean end() {
                if (heights[0] == null) {
                    heights[0] = h;
                } else if (Math.abs(heights[0] - h) > 1) {
                    return false;
                } else if (heights[0] != h) {
                    if (heights[1] == null) {
                        heights[1] = h;
                    } else if (heights[1] != h) {
                        return false;
                    }
                }
                return true;
            }
        });
    }
}

Observer 패턴을 사용하지 않고도이 작업을 수행 할 수 있다고 생각하지만 이런 식으로 추론하는 것이 더 쉽습니다.


[편집] : 왜 당신은 각 측면의 높이를 취할 수 없습니다. 이 트리를 고려하십시오.

        /\
       /  \
      /    \
     /      \_____
    /\      /     \_
   /  \    /      / \
  /\   C  /\     /   \
 /  \    /  \   /\   /\
A    B  D    E F  G H  J

OK, 조금 지저분하지만 루트의 각 측면은 균형 : C깊이 2되고 A, B, D, E깊이 3,있다가 F, G, H, J깊이 4. 왼쪽 지점의 높이입니다은 2 (높이 기억은 트래버스 당신으로 감소 분기) 오른쪽 지점의 높이가 3입니다 그러나 전체 트리입니다 하지 둘 사이의 높이 차이가로 균형 CF . 최소 최대 사양이 필요합니다 (허용되는 높이가 두 개만 있어야하므로 실제 알고리즘이 덜 복잡 할 수 있음).


답변

이것은 트리의 최상위 레벨이 균형을 이루는 지 여부 만 결정합니다. 즉, 맨 왼쪽과 맨 오른쪽에 두 개의 긴 가지가 있고 가운데에 아무것도없는 나무가있을 수 있으며 이것은 true를 반환합니다. true를 반환하기 전에 root.left및 을 재귀 적으로 확인하여 root.right내부적으로 균형이 맞는지 확인해야 합니다.


답변

보너스 운동 반응. 간단한 해결책. 분명히 실제 구현에서는 사용자가 응답에 높이를 포함하도록 요구하지 않기 위해 이것 또는 무언가를 래핑 할 수 있습니다.

IsHeightBalanced(tree, out height)
    if (tree is empty)
        height = 0
        return true
    balance = IsHeightBalanced(tree.left, heightleft) and IsHeightBalanced(tree.right, heightright)
    height = max(heightleft, heightright)+1
    return balance and abs(heightleft - heightright) <= 1     


답변

주문 후 솔루션, 트리를 한 번만 횡단합니다. 시간 복잡도는 O (n), 공간은 O (1), 하향식 솔루션보다 낫습니다. Java 버전 구현을 제공합니다.

public static <T> boolean isBalanced(TreeNode<T> root){
    return checkBalance(root) != -1;
}

private static <T> int checkBalance(TreeNode<T> node){
    if(node == null) return 0;
    int left = checkBalance(node.getLeft());

    if(left == -1) return -1;

    int right = checkBalance(node.getRight());

    if(right == -1) return -1;

    if(Math.abs(left - right) > 1){
        return -1;
    }else{
        return 1 + Math.max(left, right);
    }
}


답변

높이 균형 이진 트리의 정의는 다음과 같습니다.

모든 노드 의 두 하위 트리 높이 가 1보다 크지 않은 이진 트리

따라서 빈 이진 트리는 항상 높이가 균형을 이룹니다.
비어 있지 않은 이진 트리는 다음과 같은 경우 높이 균형을 이룹니다.

  1. 왼쪽 하위 트리는 높이가 균형을 이룹니다.
  2. 오른쪽 하위 트리는 높이가 균형을 이룹니다.
  3. 왼쪽 및 오른쪽 하위 트리의 높이 차이는 1보다 크지 않습니다.

트리를 고려하십시오.

    A
     \
      B
     / \
    C   D

보시다시피의 왼쪽 하위 트리는 A높이가 균형을 이루며 (비어 있기 때문에) 오른쪽 하위 트리도 마찬가지입니다. 그러나 왼쪽 하위 트리의 0높이가 오른쪽 하위 트리의 높이가이므로 조건 3이 충족되지 않으므로 트리는 높이 균형이 맞지 않습니다.2 .

또한 다음 트리는 왼쪽 및 오른쪽 하위 트리의 높이가 동일하더라도 높이 균형이 맞지 않습니다. 기존 코드는 이에 대해 true를 반환합니다.

       A
     /  \
    B    C
   /      \
  D        G
 /          \
E            H

그래서 단어 마다 def의 는 매우 중요합니다.

이것은 작동합니다 :

int height(treeNodePtr root) {
        return (!root) ? 0: 1 + MAX(height(root->left),height(root->right));
}

bool isHeightBalanced(treeNodePtr root) {
        return (root == NULL) ||
                (isHeightBalanced(root->left) &&
                isHeightBalanced(root->right) &&
                abs(height(root->left) - height(root->right)) <=1);
}

Ideone 링크


답변

바이너리 트리가 균형을 이루고 있거나 레벨 순서 순회로 확인할 수없는 경우 :

private boolean isLeaf(TreeNode root) {
    if (root.left == null && root.right == null)
        return true;
    return false;
}

private boolean isBalanced(TreeNode root) {
    if (root == null)
        return true;
    Vector<TreeNode> queue = new Vector<TreeNode>();
    int level = 1, minLevel = Integer.MAX_VALUE, maxLevel = Integer.MIN_VALUE;
    queue.add(root);
    while (!queue.isEmpty()) {
        int elementCount = queue.size();
        while (elementCount > 0) {
            TreeNode node = queue.remove(0);
            if (isLeaf(node)) {
                if (minLevel > level)
                    minLevel = level;
                if (maxLevel < level)
                    maxLevel = level;
            } else {
                if (node.left != null)
                    queue.add(node.left);
                if (node.right != null)
                    queue.add(node.right);
            }
            elementCount--;
        }
        if (abs(maxLevel - minLevel) > 1) {
            return false;
        }
        level++;
    }

    return true;
}