二分木がバランスしているかどうかを判断するにはどうすればよいですか?


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

これは良い実装ですか?または私は何かを逃していますか?


ドナルフェローのASCIIバイナリツリーをグラフィックで表示したい場合:i.imgur.com/97C27Ek.png
user7643681

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)

それを選択したプログラミング言語に翻訳することは簡単です。

おまけの演習:この単純なコードスケッチは、高さを計算するときに、ツリーを何回もトラバースします。より効率的にできますか?

スーパーボーナスエクササイズ:ツリーが非常に不均衡であると仮定します。同様に、片側が100万ノード、反対側が3ノードです。このアルゴリズムがスタックを爆破するシナリオはありますか?非常に不均衡なツリーが与えられた場合でも、スタックを爆破しないように実装を修正できますか?

更新:ドナルフェローは彼の回答の中で、「バランスの取れた」にはさまざまな定義があることを指摘しています。たとえば、「高さバランス」のより厳密な定義を取り、最も近い空の子へのパスの長さが、最も遠い空の子へのパスの1つ内にあることを要求できます。私の定義はそれより厳格ではないので、より多くの木を認めます。

また、私の定義よりも厳しくない場合もあります。バランスの取れたツリーは、各ブランチの空のツリーへの最大パス長が2、3、またはその他の定数以下しか異なるものとは言えません。または、最大パス長は、最小パス長の半分、たとえば半分または4分の1です。

それは通常問題ではありません。ツリーバランシングアルゴリズムの要点は、片側に100万個のノードがあり、もう片方に3個のノードがある状況で巻き込まれないようにすることです。ドナルの定義は理論的には問題ありませんが、実際には、その厳格さのレベルを満たすツリーバランシングアルゴリズムを考え出すのは面倒です。パフォーマンスの節約は通常、実装コストを正当化しません。実際にはほとんど影響を与えないバランスのレベルを達成するために、不必要なツリーの再配置を行うのに多くの時間を費やしています。理論上、完全にバランスの取れたツリーで20本しか取れない場合、100万ノードの不完全にバランスの取れたツリーで最も遠い葉に到達するのに40本の枝が必要な場合があります。ポイントは、それが百万をとらないことです。100万の最悪のケースから40の最悪のケースに至るまで、通常は十分です。最適なケースにすべて行く必要はありません。


19
正解のみ
+1、8

1
下の「演習」に答えて…
Potatoswatter

以下のボーナス演習に回答しました。
ブライアン

以下のsdkの答えは正しいようで、ツリートラバーサルは2つだけなので、O(n)も同じです。私が何かを逃していない限り、それは少なくともあなたの最初のボーナス質問を解決しません。もちろん、動的プログラミングとソリューションを使用して中間の高さをキャッシュすることもできます
Aly

理論的には、まだドナルフェローの定義に同意する必要があります。
Dhruv Gairola

26

バランスは本当に微妙な特性です。あなたはあなたがそれが何であるかを知っていると思います、しかしそれは間違えるのがとても簡単です。特に、Eric Lippert氏の(良い)答えでさえ、オフになっています。高さの概念が十分ではないからです。木の高さの最小値と最大値の概念が必要です(最小の高さは、ルートから葉までの最小ステップ数であり、最大値は...ええと、画像が表示されます)。この場合、バランスを次のように定義できます。

ブランチの最大の高さがブランチの最小の高さの1を超えないツリー。

(これは実際にはブランチ自体がバランスが取れていることを意味します。最大と最小の両方に同じブランチを選択できます。)

このプロパティを確認するために必要なのは、現在の深度を追跡する単純なツリートラバーサルだけです。初めてバックトラックすると、ベースラインの深さがわかります。その後、バックトラックするたびに、新しい深さをベースラインと比較します

  • それがベースラインと等しい場合は、続行します
  • 異なる場合は、ツリーのバランスが取れていません
  • 1オフの場合は、バランスの範囲がわかったので、後続のすべての深度(バックトラックしようとしているとき)は最初の値か2番目の値でなければなりません。

コードで:

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はABDE深さ3であり、そしてFGHJ左のブランチの高さ深さ4である(あなたが横切るように高さが減少する2覚えています枝)、右枝の高さは3です。しかし、との間には2の高さの違いがあるため、ツリー全体のバランス取れていません。ミニマックス仕様が必要です(ただし、実際のアルゴリズムは、許可される高さが2つしかないため、それほど複雑ではありません)。CF


ああ、良い点。あなたはh(LL)= 4、h(LR)= 3、h(RL)= 3、h(RR)= 2であるツリーを持つことができます。したがって、h(L)= 4およびh(R)= 3なので、以前のアルゴリズムとバランスが取れているように見えますが、最大/最小深度が4/2の場合、これはバランスが取れていません。これはおそらく画像でより理にかなっています。
Tim

1
これが私が追加したものです(世界で最も厄介なASCIIグラフィックツリー)。
ドナルフェロー

@DonalFellows:左枝の高さは2であると述べましたが、左枝にはルートとリーフAを含む4つのノードがあります。この場合、高さは3になります
ブレインストーム

22

これは、ツリーの最上位レベルのバランスが取れているかどうかを決定するだけです。つまり、左端と右端から2本の長い枝があり、中央に何もないツリーがあり、これはtrueを返します。あなたは再帰的にチェックする必要があるroot.leftroot.right彼らは内部的に真を返す前にもバランスが取れているかどうかを確認します。


ただし、コードに最大と最小の高さのメソッドがある場合、グローバルにバランスが取れていると、ローカルにもバランスが取れます。
Ari

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、2015年

あなたが思いついたこの疑似コードですか、それとも本当の言語ですか?(私は「意味out height」変数表記)
KAP

@kap:これは疑似コードですが、out構文はC#から取得されます。基本的に、それはパラメーターが呼び出された関数から呼び出し元に移動することを意味します(呼び出し元から呼び出された関数に移動する標準パラメーター、または呼び出し元から呼び出された関数に移動するrefパラメーターとは対照的)。これにより、関数は複数の値を返すことができます。
ブライアン

20

注文後のソリューション。ツリーを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);
    }
}

4
良い解決策ですが、空間の複雑さはO(H)である必要があります。ここで、Hは木の高さです。これは、再帰のためのスタック割り当てのためです。
レグラス

どういうleft == -1意味ですか?いつそうなりますか?left == -1左の子のすべてのサブツリーが不均衡である場合、再帰呼び出しがそれが真であることを意味すると想定しますか?
アスペン

left == 1左のサブツリーが不均衡であり、ツリー全体が不均衡であることを意味します。正しいサブツリーをチェックする必要がなくなり、を返すことができ-1ます。
2016

すべての要素を通過する必要があるため、時間の複雑さはO(n)です。また、ノードがx個あり、バランスを確認するのにy時間かかる場合。ノードが2つある場合は、バランスをチェックするのに2年かかります。それはすべて正しく聞こえますか?
ジャック

描画とまあの説明はここにある:algorithms.tutorialhorizo​​n.com/...
Shir

15

高さバランスのとれた二分木の定義は次のとおりです。

すべてのノードの2つのサブツリーの高さが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);
}

イデオネリンク


だからこの答えは私を大いに助けました。しかし、無料の[MITアルゴリズム入門]は条件3と矛盾しているようです。4ページ目に、左の枝の高さが2で右の枝が4のRBツリーが表示されています。説明を教えていただけますか?おそらく、サブツリーの定義がわかりません。[1]: ocw.mit.edu/courses/electrical-engineering-and-computer-science/...
i8abug

違いはコースノートのこの定義に由来するようです。任意のノードxから子孫の葉へのすべての単純なパスには、同じ数の黒いノードがあります= black-height(x)
i8abug

ただフォローアップするために、「すべての葉は根から他のどの葉よりも「特定の距離以下」である」というあなたの答えのポイント(3)を変更する定義を見つけました。これは両方のケースを満たしているようです。 ここにいくつかのランダムな
コースウェア

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
すばらしい答えです。エリックがボーナスとスーパーボーナスに関して投稿したすべての要件を満たしていると思います。これは(キューを使用して)反復的であり、再帰的ではありません。そのため、呼び出しスタックがオーバーフローすることはなく、すべてのメモリの問題をヒープに移動します。ツリー全体を走査する必要さえありません。これはレベルごとに移動するため、ツリーが1つのサイドに著しく不均衡な場合、それは本当にすぐに見つかります(最も早いですか?ほとんどの再帰アルゴリズムよりも早くなりますが、最後のレベルを見つけるポストオーダートラバース反復アルゴリズムを実装できます)アンバランスは早くなりますが、最初のレベルではうまく機能しません)。したがって、+ 1 :-)
デビッドレファエリ2018年

7

これは実際よりもずっと複雑になっています。

アルゴリズムは次のとおりです。

  1. A =最上位ノードの深度とする
  2. B =最下位レベルのノードの深度とする

  3. abs(AB)<= 1の場合、ツリーのバランスが取れている


シンプルでストレート!
Wasim Thabraze、2014

3
2つの問題があります。効率が悪くなる可能性があります。ツリー全体を2回パスすることになります。そして、左側に1つのノードがあり、右側に数千のノードがあるツリーの場合、3つのチェックの後で停止した可能性があるときに、不必要に全体を処理します。
Eric Leschinski、2015年

5

バランスのとれる意味は、手元の構造によって多少異なります。たとえば、Bツリーはルートから一定の深さ以上のノードを持つことはできません。さらに言えば、ルートから一定の深さですべてのデータが存在しますが、葉から葉への分布が分散している場合、バランスが崩れる可能性があります。 -しかし、1つのノードが均一ではありません。スキップリストバランスの概念はまったくなく、まともなパフォーマンスを実現する確率に依存しています。フィボナッチツリーは意図的にバランスが崩れ、時折長い更新と引き換えに、再バランスを延期して優れた漸近パフォーマンスを実現します。AVLおよびRed-Blackツリーは、各ノードにメタデータを付加して、深度バランス不変式を実現します。

これらのすべての構造は、ほとんどの一般的なプログラミングシステムの標準ライブラリに存在します(python、RAGEを除く)。1つまたは2つを実装することはプログラミングの良い習慣ですが、問題が、既製のコレクションで満たす必要のない独特のパフォーマンスを持たない限り、本番環境にロールバックするための適切な時間の使用とは言えません。


4

注1:サブツリーの高さは一度だけ計算されます。

注2:左側のサブツリーが不均衡である場合、右側のサブツリー(100万個の要素を含む可能性がある)の計算はスキップされます。

// 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 /赤黒)。実際、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.Aツリーのルートには常に0深さがあります
Cratylus

3

これは、C#で完全にテストされたテスト済みソリューションです(申し訳ありませんが、Java開発者ではありません)(コンソールアプリにコピーして貼り付けてください)。バランスの定義はさまざまなので、誰もが私のテスト結果を気に入らないかもしれませんが、再帰ループで深さ/高さをチェックし、各ノードのノードの高さ/レベル/深さを保存せずに最初の不一致で終了するわずかに異なるアプローチを見てください(関数呼び出しでのみ維持)。

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 2016

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)空間で動作する、最初の質問で提供されたすべての規定を満たす必要があります。メモリオーバーフローは、再帰的な呼び出しスタックをブローするのではなく、ヒープに送られます。

あるいは、最初にツリーをトラバースして、各ルートサブツリーの最大高さを繰り返し計算してキャッシュすることもできます。次に、別の反復実行で、各ルートの左サブツリーと右サブツリーのキャッシュされた高さが決して2以上異なることがないかどうかを確認します。これはO(n)時間とO(n)スペースでも実行されますが、スタックオーバーフローを引き起こさないように繰り返し実行されます。


1

さて、あなたは左と右の高さ、そして左と右のバランスが取れているかどうかを決定する方法が必要です。

そして私は return height(node->left) == height(node->right);

height関数の作成については、「再帰について」をお読みください。


3
左右の高さを1以内にする必要がありますが、必ずしも同じにする必要はありません。
アレックス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をnullノードとします。null以外のツリーノードを(LEFT VALUE RIGHT)と表します。ツリー(x A(x B x))を考えます。「ルート」はノードA、B、A、B、A、Bを永久に指します。もう一度やり直しますか?ヒント:実際には、親ポインターがなくても簡単です。
Eric Lippert、

@エリック:おっと、修正されました(そう思います)。さて、私はO(深さ)メモリなしでこれを実行しようとしています。構造体に親ポインターがない場合(多くの場合)、スタックを使用する必要があります。
Potatoswatter

つまり、親ポインタでO(n)永続メモリを使用して、O(d)一時メモリを割り当てないようにします。ここで、log n <= d <= n?これは誤った経済のようです。
Eric Lippert、

残念ながら、トラバーサルの問題は修正しましたが、ここにははるかに大きな問題があります。これは、ツリーのバランスが取れているかどうかをテストするのではなく、ツリーの葉がすべて同じレベルに近いかどうかをテストします。それは私が与えた「バランスの取れた」の定義ではありません。ツリーを考えます(((((x D x)C x)B x)A x)。あなたのコードは、明らかに最大に不均衡である場合、これが「均衡」であると報告します。もう一度やり直しますか?
Eric Lippert、

@エリックの返答1:親ポインターを他のものにすでに使用している場合は、誤った経済ではありません。回答2:確かに、それはなぜですか。これは奇妙なデバッグ方法です...私は午前4時に何のトラバーサルも盲目的に書くべきではありません...
Potatoswatter

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
回答に説明を追加したり、コードサンプルにコメントを追加したりする必要があります。
ブラッドキャンベル

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
多くのバランスの取れた木も失敗します。
ブライアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.