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


113

그 학창 시절부터 오래되었습니다. 병원에서 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;
}

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


Donal Fellows의 ascii 바이너리 트리를 그래픽으로
보고 싶다면

1
좋은 대답은 제가 미국에 들어가는 데 도움이되었습니다. (농담)
Henry

답변:


165

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

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

사양 : 잘 구성된 이진 트리는 (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으로 줄이면 보통 충분합니다. 최적의 경우로 끝까지 갈 필요가 없습니다.


19
정답에만 +1, 8 개월 동안
아무도이

1
아래 "연습"에 대한 답변…
Potatoswatter 2010

아래 보너스 연습에 답변했습니다.
Brian

아래의 sdk의 대답은 옳은 것처럼 보이며 2 개의 트리 순회 만 수행하므로 O (n)입니다. 내가 뭔가를 놓치고 있지 않는 한, 적어도 첫 번째 보너스 질문은 해결되지 않습니다. 물론 동적 프로그래밍과 솔루션을 사용하여 중간 높이를 캐시 할 수도 있습니다
Aly

이론적으로 나는 Donal Fellows의 정의를 여전히 옹호해야 할 것입니다.
Dhruv Gairola

26

균형은 정말 미묘한 속성입니다. 당신은 그것이 무엇인지 알고 있다고 생각하지만 잘못되기가 너무 쉽습니다. 특히 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 . 최소 최대 사양이 필요합니다 (허용되는 높이가 두 개만 있어야하므로 실제 알고리즘이 덜 복잡 할 수 있음).


아, 좋은 지적입니다. h (LL) = 4, h (LR) = 3, h (RL) = 3, h (RR) = 2 인 트리를 가질 수 있습니다. 따라서 h (L) = 4 및 h (R) = 3이므로 이전 알고리즘과 균형을 이루는 것처럼 보이지만 최대 / 최소 깊이가 4/2이면 균형이 맞지 않습니다. 이것은 아마도 사진으로 더 이해 될 것입니다.
Tim

1
이것이 제가 방금 추가 한 것입니다 (세계에서 가장 까다로운 ASCII 그래픽 트리로).
Donal Fellows

@DonalFellows : 당신은 왼쪽 지점의 높이가 2입니다했으나 왼쪽 분기 높이는이 경우 정확한 3 할 것이다 뿌리와 잎 A.을 포함, 4 개 개의 노드가 언급 한
두뇌 폭풍

22

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


그러나 코드에 최대 및 최소 높이 방법이있는 경우 전역 적으로 균형이 잡혀 있으면 로컬에서도 균형이 잡 힙니다.
아리

22

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

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     

트리가 수백 개의 레이어 높이보다 크면 stackoverflow 예외가 발생합니다. 효율적으로 수행했지만 중형 또는 대형 크기의 데이터 세트를 처리하지 않습니다.
Eric Leschinski

이 의사 코드는 방금 생각해 낸 것입니까 아니면 실제 언어입니까? ( " out height"변수 표기법을 의미합니다 )
kap

@kap : 이것은 의사 코드이지만 out 구문 은 C #에서 가져옵니다. 기본적으로 이는 매개 변수가 호출 된 함수에서 호출자로 이동 함을 의미합니다 (호출자에서 호출 된 함수로 이동하는 표준 매개 변수 또는 호출자에서 호출 된 함수로 이동하는 참조 매개 변수와는 반대로). 이렇게하면 함수가 둘 이상의 값을 효과적으로 반환 할 수 있습니다.
Brian

20

주문 후 솔루션, 트리를 한 번만 횡단합니다. 시간 복잡도는 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);
    }
}

4
좋은 해결책이지만 공간 복잡도는 O (H) 여야합니다. 여기서 H는 나무의 높이입니다. 재귀를위한 스택 할당 때문입니다.
legrass

무슨 left == -1뜻이야? 언제 그럴까요? 재귀 호출 left == -1이 왼쪽 자식의 모든 하위 트리가 균형이 맞지 않으면 참이 된다고 가정 합니까?
Aspen

left == 1왼쪽 하위 트리가 균형이 맞지 않고 전체 트리가 균형이 맞지 않음을 의미합니다. 더 이상 올바른 하위 트리를 확인할 필요가 없으며을 반환 할 수 있습니다 -1.
tning

모든 요소를 ​​거쳐야하기 때문에 시간 복잡도는 O (n)입니다. 그리고 만약 당신이 x 개의 노드를 가지고 있고 균형을 확인하는데 y 시간이 걸릴 것입니다; 2x 노드가있는 경우 균형을 확인하는 데 2 ​​년이 걸립니다. 그게 다 맞아?
Jack

그림에 대한 설명은 여기에 있습니다 : algorithms.tutorialhorizon.com/…
Shir

15

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

모든 노드 의 두 하위 트리 높이 가 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 링크


그래서이 대답은 저에게 많은 도움이되었습니다. 그러나 무료 [MIT 알고리즘 입문 과정]이 조건 3과 모순되는 것 같다는 것을 발견했습니다. 4 페이지는 왼쪽 가지의 높이가 2이고 오른쪽 가지가 4 인 RB 트리를 보여줍니다. 설명해 주시겠습니까? 아마도 하위 트리의 정의를 얻지 못했을 것입니다. [1] : ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
i8abug

차이점은 코스 노트의이 정의에서 비롯된 것 같습니다. 임의의 노드 x에서 하위 리프까지의 모든 단순 경로는 동일한 수의 검은 색 노드를 갖습니다 = black-height (x)
i8abug

후속 조치를 위해 "모든 잎은 다른 잎보다 뿌리에서 '특정 거리 이상 떨어져 있지 않다'"는 답변에서 포인트 (3)을 변경하는 정의를 찾았습니다. 이것은 두 경우 모두를 만족시키는 것 같습니다. 다음 은 무작위 코스웨어의 링크입니다
i8abug 2011 년

8

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

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;
}

1
훌륭한 대답입니다. Eric이 Bonus 및 Super-Bonus에 대해 게시 한 모든 요구 사항을 충족한다고 생각합니다. 반복적이며 (큐 사용) 재귀 적이 지 않습니다. 따라서 호출 스택이 오버플로되지 않고 모든 메모리 문제를 힙으로 이동합니다. 전체 트리를 순회 할 필요도 없습니다. 레벨 단위로 이동하므로 트리가 한쪽으로 크게 균형이 맞지 않으면 실제로 곧 찾을 것입니다 (가장 빨리? 대부분의 재귀 알고리즘보다 훨씬 빨리, 마지막 레벨을 찾을 순서 후 순회 반복 알고리즘을 구현할 수 있음) 불균형이 더 빨리 일어나지 만 첫 번째 수준에서는 더 나빠질 것입니다). +1 :-) 그래서
데이비드 Refaeli를

7

이것은 실제보다 훨씬 더 복잡하게 만들어지고 있습니다.

알고리즘은 다음과 같습니다.

  1. A = 최상위 노드의 깊이
  2. B = 최하위 노드의 깊이

  3. abs (AB) <= 1이면 트리가 균형을 이룹니다.


간단하고 똑바로!
Wasim Thabraze

3
두 가지 문제는 가능한 한 효율적이지 않습니다. 전체 트리를 두 번 통과하게됩니다. 그리고 왼쪽에 하나의 노드가 있고 오른쪽에 수천 개의 노드가있는 트리의 경우 3 번의 확인 후에 멈출 수 있었을 때 불필요하게 전체를 통과합니다.
Eric Leschinski

5

균형 잡힌 의미는 현재 구조에 따라 다릅니다. 예를 들어, A B-Tree는 루트에서 특정 깊이 이상의 노드를 가질 수 없으며, 그 문제에 대해 모든 데이터는 루트에서 고정 된 깊이에 살 수 있지만 잎이 잎으로 분산되면 균형이 맞지 않을 수 있습니다. -하지만 하나의 노드는 고르지 않습니다. 건너 뛰기 목록 균형에 대한 개념이 전혀 없으며 대신 적절한 성능을 달성 할 확률에 의존합니다. 피보나치 나무는 의도적으로 균형을 잃고 때때로 더 긴 업데이트를받는 대신 우수한 점근 적 성능을 달성하기 위해 재조정을 연기합니다. AVL 및 Red-Black 트리는 메타 데이터를 각 노드에 연결하여 깊이 균형 불변을 얻습니다.

이러한 모든 구조와 그 이상은 가장 일반적인 프로그래밍 시스템의 표준 라이브러리에 있습니다 (python, RAGE! 제외). 하나 또는 두 개를 구현하는 것은 좋은 프로그래밍 관행이지만, 문제가 기성 컬렉션에서 만족할 필요가없는 특정 성능을 가지고 있지 않는 한 프로덕션을 위해 자체적으로 롤링하는 데 시간을 잘 사용하지 않을 수 있습니다.


4

참고 1 : 하위 트리의 높이는 한 번만 계산됩니다.

참고 2 : 왼쪽 하위 트리의 균형이 맞지 않으면 잠재적으로 백만 개의 요소를 포함하는 오른쪽 하위 트리의 계산을 건너 뜁니다.

// return height of tree rooted at "tn" if, and only if, it is a balanced subtree
// else return -1
int maxHeight( TreeNode const * tn ) {
    if( tn ) {
        int const lh = maxHeight( tn->left );
        if( lh == -1 ) return -1;
        int const rh = maxHeight( tn->right );
        if( rh == -1 ) return -1;
        if( abs( lh - rh ) > 1 ) return -1;
        return 1 + max( lh, rh );
    }
    return 0;
}

bool isBalanced( TreeNode const * root ) {
    // Unless the maxHeight is -1, the subtree under "root" is balanced
    return maxHeight( root ) != -1;
}

3

균형은 일반적으로 각 방향에서 가장 긴 경로의 길이에 따라 달라집니다. 위의 알고리즘은 당신을 위해 그렇게하지 않을 것입니다.

무엇을 구현하려고합니까? 주변에는 자체 균형 트리가 있습니다 (AVL / Red-black). 실제로 Java 트리는 균형을 이룹니다.



3
public boolean isBalanced(TreeNode root)
{
    return (maxDepth(root) - minDepth(root) <= 1);
}

public int maxDepth(TreeNode root)
{
    if (root == null) return 0;

    return 1 + max(maxDepth(root.left), maxDepth(root.right));
}

public int minDepth (TreeNode root)
{
    if (root == null) return 0;

    return 1 + min(minDepth(root.left), minDepth(root.right));
}

이 솔루션이 올바르지 않다고 생각합니다. 단일 노드, 즉 루트가있는 트리를 전달하면 maxDepth 1(minDepth와 동일) 로 반환됩니다 . 정확한 깊이는이어야합니다 0. 나무의 뿌리에는 항상 0깊이가 있습니다
Cratylus

3

다음은 C #에서 테스트 된 완전한 솔루션입니다 (죄송합니다. 저는 자바 개발자가 아닙니다) (콘솔 앱에 붙여 넣기 만하면됩니다). 균형의 정의가 다양하므로 모든 사람이 내 테스트 결과를 좋아하는 것은 아니지만 재귀 루프에서 깊이 / 높이를 확인하고 각 노드에서 노드 높이 / 레벨 / 깊이를 저장하지 않고 첫 번째 불일치에서 나가는 약간 다른 접근 방식을 살펴보십시오. (함수 호출에서만 유지).

using System;
using System.Linq;
using System.Text;

namespace BalancedTree
{
    class Program
    {
        public static void Main()
        {
            //Value Gathering
            Console.WriteLine(RunTreeTests(new[] { 0 }));
            Console.WriteLine(RunTreeTests(new int[] { }));

            Console.WriteLine(RunTreeTests(new[] { 0, 1, 2, 3, 4, -1, -4, -3, -2 }));
            Console.WriteLine(RunTreeTests(null));
            Console.WriteLine(RunTreeTests(new[] { 10, 8, 12, 8, 4, 14, 8, 10 }));
            Console.WriteLine(RunTreeTests(new int[] { 20, 10, 30, 5, 15, 25, 35, 3, 8, 12, 17, 22, 27, 32, 37 }));

            Console.ReadKey();
        }

        static string RunTreeTests(int[] scores)
        {
            if (scores == null || scores.Count() == 0)
            {
                return null;
            }

            var tree = new BinarySearchTree();

            foreach (var score in scores)
            {
                tree.InsertScore(score);
            }

            Console.WriteLine(tree.IsBalanced());

            var sb = tree.GetBreadthWardsTraversedNodes();

            return sb.ToString(0, sb.Length - 1);
        }
    }

    public class Node
    {
        public int Value { get; set; }
        public int Count { get; set; }
        public Node RightChild { get; set; }
        public Node LeftChild { get; set; }
        public Node(int value)
        {
            Value = value;
            Count = 1;
        }

        public override string ToString()
        {
            return Value + ":" + Count;
        }

        public bool IsLeafNode()
        {
            return LeftChild == null && RightChild == null;
        }

        public void AddValue(int value)
        {
            if (value == Value)
            {
                Count++;
            }
            else
            {
                if (value > Value)
                {
                    if (RightChild == null)
                    {
                        RightChild = new Node(value);
                    }
                    else
                    {
                        RightChild.AddValue(value);
                    }
                }
                else
                {
                    if (LeftChild == null)
                    {
                        LeftChild = new Node(value);
                    }
                    else
                    {
                        LeftChild.AddValue(value);
                    }
                }
            }
        }
    }

    public class BinarySearchTree
    {
        public Node Root { get; set; }

        public void InsertScore(int score)
        {
            if (Root == null)
            {
                Root = new Node(score);
            }
            else
            {
                Root.AddValue(score);
            }
        }

        private static int _heightCheck;
        public bool IsBalanced()
        {
            _heightCheck = 0;
            var height = 0;
            if (Root == null) return true;
            var result = CheckHeight(Root, ref height);
            height--;
            return (result && height == 0);
        }

        private static bool CheckHeight(Node node, ref int height)
        {
            height++;
            if (node.LeftChild == null)
            {
                if (node.RightChild != null) return false;
                if (_heightCheck != 0) return _heightCheck == height;
                _heightCheck = height;
                return true;
            }
            if (node.RightChild == null)
            {
                return false;
            }

            var leftCheck = CheckHeight(node.LeftChild, ref height);
            if (!leftCheck) return false;
            height--;
            var rightCheck = CheckHeight(node.RightChild, ref height);
            if (!rightCheck) return false;
            height--;
            return true;
        }


        public StringBuilder GetBreadthWardsTraversedNodes()
        {
            if (Root == null) return null;
            var traversQueue = new StringBuilder();
            traversQueue.Append(Root + ",");
            if (Root.IsLeafNode()) return traversQueue;
            TraversBreadthWards(traversQueue, Root);
            return traversQueue;
        }

        private static void TraversBreadthWards(StringBuilder sb, Node node)
        {
            if (node == null) return;
            sb.Append(node.LeftChild + ",");
            sb.Append(node.RightChild + ",");
            if (node.LeftChild != null && !node.LeftChild.IsLeafNode())
            {
                TraversBreadthWards(sb, node.LeftChild);
            }
            if (node.RightChild != null && !node.RightChild.IsLeafNode())
            {
                TraversBreadthWards(sb, node.RightChild);
            }
        }
    }
}

누군가가 답변을 게시 한 후 2 분 이내에이 답변에 반대 투표를 할 수 있는지 이해할 수 없습니다.? 반대표는 괜찮지 만이 솔루션의 문제점을 설명해 주시겠습니까?
sbp

2
#include <iostream>
#include <deque>
#include <queue>

struct node
{
    int data;
    node *left;
    node *right;
};

bool isBalanced(node *root)
{
    if ( !root)
    {
        return true;
    }

    std::queue<node *> q1;
    std::queue<int>  q2;
    int level = 0, last_level = -1, node_count = 0;

    q1.push(root);
    q2.push(level);

    while ( !q1.empty() )
    {
        node *current = q1.front();
        level = q2.front();

        q1.pop();
        q2.pop();

        if ( level )
        {
            ++node_count;
        }

                if ( current->left )
                {
                        q1.push(current->left);
                        q2.push(level + 1);
                }

                if ( current->right )
                {
                        q1.push(current->right);
                        q2.push(level + 1);
                }

        if ( level != last_level )
        {
            std::cout << "Check: " << (node_count ? node_count - 1 : 1) << ", Level: " << level << ", Old level: " << last_level << std::endl;
            if ( level && (node_count - 1) != (1 << (level-1)) )
            {
                return false;
            }

            last_level = q2.front();
            if ( level ) node_count = 1;
        }
    }

    return true;
}

int main()
{
    node tree[15];

    tree[0].left  = &tree[1];
    tree[0].right = &tree[2];
    tree[1].left  = &tree[3];
    tree[1].right = &tree[4];
    tree[2].left  = &tree[5];
    tree[2].right = &tree[6];
    tree[3].left  = &tree[7];
    tree[3].right = &tree[8];
    tree[4].left  = &tree[9];   // NULL;
    tree[4].right = &tree[10];  // NULL;
    tree[5].left  = &tree[11];  // NULL;
    tree[5].right = &tree[12];  // NULL;
    tree[6].left  = &tree[13];
    tree[6].right = &tree[14];
    tree[7].left  = &tree[11];
    tree[7].right = &tree[12];
    tree[8].left  = NULL;
    tree[8].right = &tree[10];
    tree[9].left  = NULL;
    tree[9].right = &tree[10];
    tree[10].left = NULL;
    tree[10].right= NULL;
    tree[11].left = NULL;
    tree[11].right= NULL;
    tree[12].left = NULL;
    tree[12].right= NULL;
    tree[13].left = NULL;
    tree[13].right= NULL;
    tree[14].left = NULL;
    tree[14].right= NULL;

    std::cout << "Result: " << isBalanced(tree) << std::endl;

    return 0;
}

당신은 몇 가지 코멘트를 추가 할 수 있습니다
jgauffin

2

RE : BFS를 사용하여 레벨 순서 순회를 수행하는 @lucky의 솔루션입니다.

트리를 탐색하고 노드가 리프 인 최소 수준을 설명하는 vars min / max-level에 대한 참조를 유지합니다.

@lucky 솔루션에는 수정이 필요하다고 생각합니다. @codaddict가 제안한대로 노드가 리프인지 확인하는 대신 왼쪽 또는 오른쪽 자식이 null (둘 다가 아님)인지 확인해야합니다. 그렇지 않으면 알고리즘은 이것을 유효한 균형 트리로 간주합니다.

     1
    / \
   2   4
    \   \
     3   1

Python에서 :

def is_bal(root):
    if root is None:
        return True

    import queue

    Q = queue.Queue()
    Q.put(root)

    level = 0
    min_level, max_level = sys.maxsize, sys.minsize

    while not Q.empty():
        level_size = Q.qsize()

        for i in range(level_size):
            node = Q.get()

            if not node.left or node.right:
                min_level, max_level = min(min_level, level), max(max_level, level)

            if node.left:
                Q.put(node.left)
            if node.right:
                Q.put(node.right)

        level += 1

        if abs(max_level - min_level) > 1:
            return False

    return True

이 솔루션은 O (n) 시간 및 O (n) 공간에서 작동하는 초기 질문에서 제공된 모든 규정을 충족해야합니다. 메모리 오버플로는 재귀 호출 스택을 날리는 대신 힙으로 전달됩니다.

또는 처음에 트리를 탐색하여 각 루트 하위 트리의 최대 높이를 반복적으로 계산 + 캐시 할 수 있습니다. 그런 다음 다른 반복 실행에서 각 루트에 대한 왼쪽 및 오른쪽 하위 트리의 캐시 된 높이가 둘 이상 다르지 않은지 확인합니다. 이것은 또한 O (n) 시간과 O (n) 공간에서 실행되지만 스택 오버플로를 일으키지 않도록 반복적으로 실행됩니다.


1

글쎄, 당신은 왼쪽과 오른쪽의 높이를 결정하는 방법이 필요하고, 왼쪽과 오른쪽이 균형을 이룬다면.

그리고 난 그냥 return height(node->left) == height(node->right);

height함수 작성에 관해서는 다음을 읽으십시오. 재귀 이해


3
왼쪽과 오른쪽 높이가 반드시 같지는 않지만 1 이내가되기를 원합니다.
Alex B

1

어떤 종류의 나무에 대해 말하고 있습니까? 있습니다 자기 균형 거기 나무. 균형을 유지하기 위해 트리를 재정렬해야하는지 결정하는 알고리즘을 확인합니다.


1

다음은 일반적인 깊이 우선 순회를 기반으로 한 버전입니다. 다른 정답보다 빠르며 언급 된 모든 "도전"을 처리해야합니다. 스타일에 대해 사과드립니다. 저는 Java를 잘 모릅니다.

max와 min이 모두 설정되어 있고 차이가 1보다 크면 일찍 돌아와서 훨씬 더 빠르게 만들 수 있습니다.

public boolean isBalanced( Node root ) {
    int curDepth = 0, maxLeaf = 0, minLeaf = INT_MAX;
    if ( root == null ) return true;
    while ( root != null ) {
        if ( root.left == null || root.right == null ) {
            maxLeaf = max( maxLeaf, curDepth );
            minLeaf = min( minLeaf, curDepth );
        }
        if ( root.left != null ) {
            curDepth += 1;
            root = root.left;
        } else {
            Node last = root;
            while ( root != null
             && ( root.right == null || root.right == last ) ) {
                curDepth -= 1;
                last = root;
                root = root.parent;
            }
            if ( root != null ) {
                curDepth += 1;
                root = root.right;
            }
        }
    }
    return ( maxLeaf - minLeaf <= 1 );
}

1
좋은 시도이지만 분명히 작동하지 않습니다. x를 널 노드로 둡니다. 널이 아닌 트리 노드를 (LEFT VALUE RIGHT)로 표시합니다. 트리 (x A (x B x))를 고려하십시오. "root"는 노드 A, B, A, B, A, B ... 영원히를 가리 킵니다. 다시 시도 하시겠습니까? 힌트 : 부모 포인터 없이는 실제로 더 쉽습니다 .
Eric Lippert

@Eric : 죄송합니다, 수정되었습니다 (제 생각에). 글쎄요, 저는 O (깊이) 메모리없이이 작업을 수행하려고합니다. 구조에 부모 포인터가 없으면 (자주 그렇습니다) 스택을 사용해야합니다.
Potatoswatter 2010

그래서 당신이 나에게 말하는 것은 O (d) 임시 메모리 할당을 피하기 위해 부모 포인터에서 O (n) 영구 메모리를 사용하는 것입니다. 여기서 log n <= d <= n? 이것은 잘못된 경제처럼 보입니다.
Eric Lippert

안타깝게도 순회 문제를 해결했지만 여기에 훨씬 더 큰 문제가 있습니다. 이것은 나무가 균형을 이루고 있는지 여부를 테스트하는 것이 아니라 나무의 모든 잎이 동일한 수준에 가깝게 있는지 여부를 테스트합니다. 그것은 내가 준 "균형"의 정의가 아닙니다. 나무 ((((x D x) C x) B x) A x)를 고려하십시오. 귀하의 코드는 이것이 분명히 최대 불균형 일 때 "균형"이라고보고합니다. 다시 시도 하시겠습니까?
Eric Lippert

@Eric 대답 1 : 이미 다른 것을 위해 부모 포인터를 사용한다면 거짓 경제가 아닙니다. 답변 2 : 물론입니다. 이것은 기괴한 디버깅 방법입니다… 오전 4시에 맹목적으로 순회를 작성해서는 안됩니다…
Potatoswatter 2010

1
/* Returns true if Tree is balanced, i.e. if the difference between the longest path and the shortest path from the root to a leaf node is no more than than 1. This difference can be changed to any arbitrary positive number. */
boolean isBalanced(Node root) {
    if (longestPath(root) - shortestPath(root) > 1)
        return false;
    else
        return true;
}


int longestPath(Node root) {
    if (root == null);
        return 0;
    else {
        int leftPathLength = longestPath(root.left);
        int rightPathLength = longestPath(root.right);
        if (leftPathLength >= rightPathLength)
            return leftPathLength + 1;
        else
            return rightPathLength + 1;
    }
}

int shortestPath(Node root) {
    if (root == null);
        return 0;
    else {
        int leftPathLength = shortestPath(root.left);
        int rightPathLength = shortestPath(root.right);
        if (leftPathLength <= rightPathLength)
            return leftPathLength + 1;
        else
            return rightPathLength + 1;
    }
}

1
답변에 대한 설명 및 / 또는 코드 샘플에 대한 주석을 추가해야합니다.
Brad Campbell

1
class Node {
    int data;
    Node left;
    Node right;

    // assign variable with constructor
    public Node(int data) {
        this.data = data;
    }
}

public class BinaryTree {

    Node root;

    // get max depth
    public static int maxDepth(Node node) {
        if (node == null)
            return 0;

        return 1 + Math.max(maxDepth(node.left), maxDepth(node.right));
    }

    // get min depth
    public static int minDepth(Node node) {
        if (node == null)
            return 0;

        return 1 + Math.min(minDepth(node.left), minDepth(node.right));
    }

    // return max-min<=1 to check if tree balanced
    public boolean isBalanced(Node node) {

        if (Math.abs(maxDepth(node) - minDepth(node)) <= 1)
            return true;

        return false;
    }

    public static void main(String... strings) {
        BinaryTree tree = new BinaryTree();
        tree.root = new Node(1);
        tree.root.left = new Node(2);
        tree.root.right = new Node(3);


        if (tree.isBalanced(tree.root))
            System.out.println("Tree is balanced");
        else
            System.out.println("Tree is not balanced");
    }
}

0

에릭의 보너스 운동을 위해 내가 시도한 것입니다. 재귀 루프를 풀고 균형이 맞지 않는 하위 트리를 찾으면 즉시 반환하려고합니다.

int heightBalanced(node *root){
    int i = 1;
    heightBalancedRecursive(root, &i);
    return i; 
} 

int heightBalancedRecursive(node *root, int *i){

    int lb = 0, rb = 0;

    if(!root || ! *i)  // if node is null or a subtree is not height balanced
           return 0;  

    lb = heightBalancedRecursive(root -> left,i);

    if (!*i)         // subtree is not balanced. Skip traversing the tree anymore
        return 0;

    rb = heightBalancedRecursive(root -> right,i)

    if (abs(lb - rb) > 1)  // not balanced. Make i zero.
        *i = 0;

    return ( lb > rb ? lb +1 : rb + 1); // return the current height of the subtree
}

0
public int height(Node node){
    if(node==null)return 0;
    else{
        int l=height(node.leftChild);
        int r=height(node.rightChild);
       return(l>r?l+1:r+1);

}}
public boolean balanced(Node n){

    int l= height(n.leftChild);
    int r= height(n.rightChild);

    System.out.println(l + " " +r);
    if(Math.abs(l-r)>1)
        return false;
    else 
        return true;
    }

0

빈 나무는 높이가 균형을 이룹니다. 비어 있지 않은 이진 트리 T는 다음과 같은 경우 균형을 이룹니다.

1) T의 왼쪽 하위 트리가 균형을 이룹니다.

2) T의 오른쪽 하위 트리가 균형을 이룹니다.

3) 왼쪽 하위 트리와 오른쪽 하위 트리의 높이 차이가 1 이하입니다.

/* program to check if a tree is height-balanced or not */
#include<stdio.h>
#include<stdlib.h>
#define bool int

/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct node
{
  int data;
  struct node* left;
  struct node* right;
};

/* The function returns true if root is balanced else false
   The second parameter is to store the height of tree.  
   Initially, we need to pass a pointer to a location with value 
   as 0. We can also write a wrapper over this function */
bool isBalanced(struct node *root, int* height)
{
  /* lh --> Height of left subtree 
     rh --> Height of right subtree */   
  int lh = 0, rh = 0;  

  /* l will be true if left subtree is balanced 
    and r will be true if right subtree is balanced */
  int l = 0, r = 0;

  if(root == NULL)
  {
    *height = 0;
     return 1;
  }

  /* Get the heights of left and right subtrees in lh and rh 
    And store the returned values in l and r */   
  l = isBalanced(root->left, &lh);
  r = isBalanced(root->right,&rh);

  /* Height of current node is max of heights of left and 
     right subtrees plus 1*/   
  *height = (lh > rh? lh: rh) + 1;

  /* If difference between heights of left and right 
     subtrees is more than 2 then this node is not balanced
     so return 0 */
  if((lh - rh >= 2) || (rh - lh >= 2))
    return 0;

  /* If this node is balanced and left and right subtrees 
    are balanced then return true */
  else return l&&r;
}


/* UTILITY FUNCTIONS TO TEST isBalanced() FUNCTION */

/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
struct node* newNode(int data)
{
    struct node* node = (struct node*)
                                malloc(sizeof(struct node));
    node->data = data;
    node->left = NULL;
    node->right = NULL;

    return(node);
}

int main()
{
  int height = 0;

  /* Constructed binary tree is
             1
           /   \
         2      3
       /  \    /
     4     5  6
    /
   7
  */   
  struct node *root = newNode(1);  
  root->left = newNode(2);
  root->right = newNode(3);
  root->left->left = newNode(4);
  root->left->right = newNode(5);
  root->right->left = newNode(6);
  root->left->left->left = newNode(7);

  if(isBalanced(root, &height))
    printf("Tree is balanced");
  else
    printf("Tree is not balanced");    

  getchar();
  return 0;
}

시간 복잡성 : O (n)


0

특히 거대한 나무에서 더 나은 성능을 얻으려면 각 노드의 높이를 절약하여 공간 대 성능을 절충 할 수 있습니다.

class Node {
    Node left;
    Node right;
    int value;
    int height;
}

추가 구현 예 및 삭제도 동일

void addNode(Node root,int v)
{    int height =0;
     while(root != null)
     {
         // Since we are adding new node so the height 
         // will increase by one in each node we will pass by
         root.height += 1;
         height++;
         else if(v > root.value){
            root = root.left();
            }
         else{
         root = root.right();
         }

     }

         height++;
         Node n = new Node(v , height);
         root = n;         
}
int treeMaxHeight(Node root)
{
 return Math.Max(root.left.height,root.right.height);
}

int treeMinHeight(Node root)
{
 return Math.Min(root.left.height,root.right.height);

}

Boolean isNodeBlanced(Node root)
{
   if (treeMaxHeight(root) - treeMinHeight(root) > 2)
       return false;

  return true;
}

Boolean isTreeBlanced (Node root)
{
    if(root == null || isTreeBalanced(root.left) && isTreeBalanced(root.right) && isNodeBlanced(root))
    return true;

  return false;

}

-1
    static boolean isBalanced(Node root) {
    //check in the depth of left and right subtree
    int diff = depth(root.getLeft()) - depth(root.getRight());
    if (diff < 0) {
        diff = diff * -1;
    }
    if (diff > 1) {
        return false;
    }
    //go to child nodes
    else {
        if (root.getLeft() == null && root.getRight() == null) {
            return true;
        } else if (root.getLeft() == null) {
            if (depth(root.getRight()) > 1) {
                return false;
            } else {
                return true;
            }
        } else if (root.getRight() == null) {
            if (depth(root.getLeft()) > 1) {
                return false;
            } else {
                return true;
            }
        } else if (root.getLeft() != null && root.getRight() != null && isBalanced(root.getLeft()) && isBalanced(root.getRight())) {
            return true;
        } else {
            return false;
        }
    }
}

-2

작동하지 않습니까?

return ( ( Math.abs( size( root.left ) - size( root.right ) ) < 2 );

불균형 트리는 항상 실패합니다.


4
균형 잡힌 많은 나무들도 실패 할 것입니다.
Brian
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.