İkili ağacın dengeli olup olmadığı nasıl belirlenir?


113

O okul yıllarından epey bir zaman geçti. Bir hastanede BT uzmanı olarak iş buldum. Şimdi gerçek bir programlama yapmak için hareket etmeye çalışıyorum. Şu anda ikili ağaçlar üzerinde çalışıyorum ve ağacın yükseklik dengeli olup olmadığını belirlemenin en iyi yolunun ne olacağını merak ediyordum.

Bunun boyunca bir şey düşünüyordum:

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

Bu iyi bir uygulama mı? yoksa bir şey mi kaçırıyorum?


Donal Fellows'un ascii ikili ağacını bir grafikle görmek isterseniz: i.imgur.com/97C27Ek.png
user7643681

1
Güzel cevap, ABD'ye girmeme yardımcı oldu. (şakalar)
Henry

Yanıtlar:


165

Başka bir şey ararken bu eski soruya rastladım. Asla tam bir cevap alamadığınızı fark ettim.

Bu sorunu çözmenin yolu, yazmaya çalıştığınız işlev için bir özellik yazarak başlamaktır.

Şartname: İyi biçimlendirilmiş bir ikili ağacın, (1) boşsa veya (2) sol ve sağ çocukları dengeli ve sol ağacın yüksekliği, sağ ağacın yüksekliği.

Artık spesifikasyona sahip olduğunuza göre, kodu yazmak önemsizdir. Sadece özellikleri takip edin:

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

Bunu seçtiğiniz programlama diline çevirmek önemsiz olmalı.

Bonus alıştırma : Bu saf kod taslağı, yükseklikleri hesaplarken ağacı çok fazla kat eder. Daha verimli hale getirebilir misin?

Süper bonus alıştırması : ağacın büyük ölçüde dengesiz olduğunu varsayalım . Bir tarafta bir milyon, diğer tarafta üç derin düğüm gibi. Bu algoritmanın yığını patlattığı bir senaryo var mı? Uygulamayı, büyük ölçüde dengesiz bir ağaç verildiğinde bile, yığını asla patlatmayacak şekilde düzeltebilir misiniz?

GÜNCELLEME : Donal Fellows cevabında, kişinin seçebileceği farklı 'dengeli' tanımları olduğuna işaret ediyor. Örneğin, "yükseklik dengeli" nin daha katı bir tanımını alabilir ve en yakın boş çocuğa giden yol uzunluğunun, en uzaktaki boş çocuğa giden yollardan biri içinde olması gerekebilir . Benim tanımım bundan daha az katıdır ve bu nedenle daha fazla ağacı kabul eder.

Benim tanımımdan daha az katı da olabilir; Dengeli bir ağacın, her daldaki boş bir ağaca giden maksimum yol uzunluğunun en fazla iki, üç veya başka bir sabit olduğu söylenebilir. Veya maksimum yol uzunluğunun, yarım veya çeyrek gibi minimum yol uzunluğunun bir kısmı olduğunu.

Genelde gerçekten önemli değil. Herhangi bir ağaç dengeleme algoritmasının amacı, bir tarafta bir milyon ve diğer tarafta üç düğümün olduğu bir duruma düşmemenizi sağlamaktır. Donal'ın tanımı teoride gayet iyi, ancak pratikte bu katılık düzeyini karşılayan bir ağaç dengeleme algoritmasıyla ortaya çıkan bir acı. Performans tasarrufları genellikle uygulama maliyetini haklı çıkarmaz. Pratikte çok az fark yaratan bir denge düzeyine ulaşmak için gereksiz ağaç düzenlemeleri yapmak için çok zaman harcıyorsunuz. Mükemmel dengelenmiş bir ağaçta teorik olarak yalnızca yirmi tane alabilecekken, mükemmel olmayan bir şekilde dengelenmiş bir milyon düğümdeki en uzaktaki yaprağa ulaşmak için bazen kırk dal alması kimin umurunda? Mesele şu ki, bir milyon bile almaz. Bir milyonun en kötü durumundan en kötü kırk duruma inmek genellikle yeterince iyidir; en iyi duruma kadar gitmek zorunda değilsiniz.


19
Sadece doğru cevap için +1, 8 aydır kimsenin buna cevap veremediğine inanamıyorum ...
BlueRaja - Danny Pflughoeft

1
Aşağıdaki "alıştırmalar"
ın cevabı

Aşağıda cevaplanmış Bonus egzersizi.
Brian

sdk'nin aşağıdaki cevabı doğru görünüyor ve sadece 2 ağaç geçişi yapıyor, yani O (n). Bir şeyi kaçırmadığım sürece, bu en azından ilk bonus sorunuzu çözmez mi? Elbette orta yükseklikleri önbelleğe almak için dinamik programlamayı ve çözümünüzü de kullanabilirsiniz
Aly

Teorik olarak, Donal Fellows'un tanımına yine de katılmam gerekirdi.
Dhruv Gairola

26

Denge gerçekten incelikli bir özelliktir; ne olduğunu bildiğini sanıyorsun, ama yanılmak çok kolay. Özellikle Eric Lippert'in (iyi) cevabı bile yanlış. Bunun nedeni, boy kavramının yeterli olmamasıdır. Bir ağacın minimum ve maksimum yüksekliği kavramına sahip olmanız gerekir (minimum yükseklik, kökten yaprağa en az adım sayısıdır ve maksimum ... peki, resmi alırsınız). Buna göre, dengeyi şöyle tanımlayabiliriz:

Herhangi bir şubesine maksimum yüksekliği en fazla olan bir ağaç biri daha herhangi şube asgari yüksekliğinden daha.

(Bu aslında dalların kendilerinin dengeli olduğu anlamına gelir; aynı dalı hem maksimum hem de minimum için seçebilirsiniz.)

Bu özelliği doğrulamak için yapmanız gereken tek şey, mevcut derinliği takip eden basit bir ağaç çapraz geçişidir. İlk kez geri döndüğünüzde, bu size bir temel derinlik verir. Bundan sonra her geri döndüğünüzde, yeni derinliği taban çizgisiyle karşılaştırırsınız.

  • taban çizgisine eşitse, devam et
  • birden fazla farklıysa ağaç dengeli değildir
  • bir defalık ise, artık denge aralığını biliyorsunuzdur ve sonraki tüm derinlikler (geri dönmek üzereyken) ya birinci ya da ikinci değer olmalıdır.

Kodda:

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

Gözlemci kalıbını kullanmadan bunu yapabileceğinizi sanıyorum, ancak bu şekilde düşünmeyi daha kolay buluyorum.


[DÜZENLE]: Neden her iki tarafın yüksekliğini alamıyorsunuz? Bu ağacı düşünün:

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

Tamam biraz karışık ama kök her iki tarafında dengelenir: Cderinlik 2, bir A, B, D, Ederinlik 3 ve vardır F, G, H, Jderinliği 4. sol kanadının yüksekliği 2 (yükseklik hatırlamak hareket size azalır dal), sağ dalın yüksekliği 3'tür. Yine de ağaç genel olarak dengeli değildir çünkü Cve arasında 2'lik bir yükseklik farkı vardır F. Bir minimum eksen spesifikasyonuna ihtiyacınız vardır (ancak gerçek algoritma daha az karmaşık olabilir, çünkü yalnızca iki izin verilen yükseklik olması gerekir).


Ah, iyi nokta. H (LL) = 4, h (LR) = 3, h (RL) = 3, h (RR) = 2 olan bir ağacınız olabilir. Böylece, h (L) = 4 ve h (R) = 3, bu nedenle önceki algoritmaya dengeli görünecektir, ancak 4/2 maksimum / min derinlik ile bu dengeli değildir. Bu muhtemelen bir resimle daha mantıklı olacaktır.
Tim

1
Az önce ekledim (dünyanın en kötü ASCII grafik ağacıyla).
Donal Fellows

@DonalFellows: Sol dalın yüksekliğinin 2 olduğunu söylediniz, ancak sol dalın kök ve A yaprağı dahil 4 düğümü var. Bu durumda yükseklik 3 olacak
beyin fırtınası

22

Bu sadece ağacın en üst seviyesinin dengeli olup olmadığını belirler. Yani, en solda ve en sağda iki uzun dalı olan, ortasında hiçbir şey olmayan bir ağaca sahip olabilirsiniz ve bu doğru olacaktır. Doğruya dönmeden önce içsel olarak dengeli olup olmadıklarını tekrar tekrar kontrol etmeniz root.leftve kontrol etmeniz gerekir root.right.


Bununla birlikte, kodun maksimum ve minimum yükseklik yöntemi varsa, küresel olarak dengelenmişse, yerel olarak da dengelenir.
Ari

22

Bonus egzersiz yanıtı. Basit çözüm. Açıktır ki gerçek bir uygulamada, kullanıcının yanıtına yüksekliği eklemesini gerektirmekten kaçınmak için bunu veya başka bir şeyi sarabilir.

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     

Ağaç birkaç yüz katmandan daha büyükse, bir yığın taşması istisnası alırsınız. Bunu verimli bir şekilde yaptınız, ancak orta veya büyük boyutlu veri kümelerini işlemiyor.
Eric Leschinski

Bu yeni bulduğunuz sözde kod mu yoksa gerçek bir dil mi? (" out height" Değişken gösterimi kastediyorum )
kap

@kap: Bu sözde koddur, ancak çıkış sözdizimi C # 'dan alınmıştır. Temel olarak, parametrenin çağrılan işlevden arayana gittiği anlamına gelir (arayandan çağrılan işleve veya ref parametrelerine giden, arayandan aranan işleve ve geri giden standart parametrelerin aksine). Bu, işlevlerin birden fazla değer döndürmesine etkili bir şekilde izin verir.
Brian

20

Sipariş sonrası çözüm, ağacı yalnızca bir kez çaprazlayın. Zaman karmaşıklığı O (n), uzay O (1), yukarıdan aşağıya çözümden daha iyidir. Size bir java sürümü uygulaması veriyorum.

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
güzel bir çözüm, ancak alan karmaşıklığı O (H) olmalı, burada H ağacın yüksekliğidir. Bunun nedeni, özyineleme için yığın tahsisidir.
legrass

Ne anlama left == -1geliyor? Bu ne zaman olacak? Yinelemeli çağrının left == -1, sol çocukların tüm alt ağaçları dengesizse bunun doğru olduğunu ima ettiğini varsayıyor muyuz ?
Aspen

left == 1sol alt ağaç dengesiz olduğu anlamına gelir, o zaman tüm ağaç dengesizdir. Artık doğru alt ağacı kontrol etmemize gerek yok ve geri dönebiliriz -1.
2016

Zaman karmaşıklığı O (n) çünkü tüm unsurlardan geçmeniz gerekiyor. Ve eğer x düğümünüz varsa ve dengeyi kontrol etmek y zaman alacak; 2x düğümünüz varsa, dengeyi kontrol etmek 2 yıl sürecektir. Her şey doğru mu?
Jack,

Çizimle ilgili güzel açıklama burada: algoritmalar.tutorialhorizon.com/…
Shir

15

Yükseklik dengeli ikili ağacın tanımı şöyledir:

Her düğümün iki alt ağacının yüksekliğinin hiçbir zaman 1'den fazla farklı olmadığı ikili ağaç .

Yani, boş bir ikili ağaç her zaman yükseklik dengelidir.
Boş olmayan bir ikili ağacın yüksekliği şu durumlarda dengelenir:

  1. Sol alt ağacının yüksekliği dengelenmiştir.
  2. Sağ alt ağacı, yükseklik dengelidir.
  3. Sol ve sağ alt ağacın yükseklikleri arasındaki fark 1'den büyük değil.

Ağacı düşünün:

    A
     \ 
      B
     / \
    C   D

Görüldüğü gibi sol alt ağacı ( Aboş olduğu için) yükseklik dengelidir ve sağ alt ağacı da öyle. Ancak yine de ağacın yüksekliği dengeli değildir, çünkü sol alt ağacın 0yüksekliği ve sağ alt ağacın yüksekliği gibi 3. koşul karşılanmaz.2 .

Ayrıca aşağıdaki ağacın sol ve sağ alt ağacın yüksekliği eşit olmasına rağmen yükseklik dengeli değildir. Mevcut kodunuz bunun için doğru olarak dönecektir.

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

Yani kelime her çok önemlidir.

Bu çalışacak:

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

İdeon Bağlantısı


Yani bu cevap bana çok yardımcı oldu. Ancak, ücretsiz [MIT algoritmalar kursuna giriş] koşulunun 3. koşulla çeliştiğini buldum. Sayfa 4, sol dalın yüksekliğinin 2 ve sağ dalın 4 olduğu bir RB ağacını gösteriyor. Bana biraz açıklama yapabilir misiniz? Belki bir alt ağacın tanımını anlamıyorum. [1]: ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
i8abug

Fark, ders notlarındaki bu tanımdan geliyor gibi görünüyor. Herhangi bir x düğümünden bir alt yaprağa giden tüm basit yollar aynı sayıda siyah düğüme sahiptir = siyah yükseklik (x)
i8abug

Sadece takip etmek için, cevabınızdaki (3) numaralı noktayı değiştiren bir tanım buldum: "Her yaprak, kökten diğer yapraklara göre 'belirli bir mesafeden daha uzaktır'. Bu her iki durumu da tatmin ediyor gibi görünüyor. İşte bazı rastgele kurs
yazılımlarından

8

İkili ağaç dengelenmişse veya Seviye sırası geçişi ile kontrol edilemiyorsa:

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
Mükemmel cevap. Bence Eric'in Bonus ve Süper Bonus ile ilgili gönderdiği tüm gereksinimleri karşılıyor. Yinelemelidir (bir kuyruk kullanarak) ve özyinelemeli değildir - bu nedenle çağrı yığını taşmaz ve tüm bellek sorunlarını yığına taşırız. Ağacın tamamını dolaşmayı bile gerektirmez. Seviye adım hareket eder, bu nedenle bir ağaç 1 tarafa büyük ölçüde dengesizse, onu gerçekten çok yakında bulacaktır (en kısa sürede - çoğu yinelemeli algoritmadan çok daha erken, ancak son seviyeyi bulacak bir sipariş sonrası geçişli yinelemeli algoritma uygulayabilirsiniz. dengesizlikler daha erken ancak ilk seviyelerde daha zayıf hareket edecek). Yani +1 :-)
David Refaeli

7

Bu, gerçekte olduğundan çok daha karmaşık hale getiriliyor.

Algoritma aşağıdaki gibidir:

  1. A = en yüksek seviyeli düğümün derinliği olsun
  2. B = en düşük seviyeli düğümün derinliği olsun

  3. Abs (AB) <= 1 ise ağaç dengelidir


Basit ve düz!
Wasim Thabraze

3
İki sorun, olabileceği kadar verimli değil, tüm ağacın üzerinden iki geçiş yapıyorsunuz. Ve solda bir düğümü ve sağda binlerce düğümü olan ağaçlar için, 3 kontrolden sonra durmuş olabileceğiniz zaman, gereksiz yere her şeye adım atarsınız.
Eric Leschinski

5

Dengeli anlam biraz eldeki yapıya bağlıdır. Örneğin, Bir B-Ağacı, kökten belirli bir derinlikten daha fazla veya daha az düğümlere sahip olamaz, tüm veriler kökten sabit bir derinlikte yaşar, ancak yaprakların yapraklara dağılımı dengesiz olabilir. -ama-bir düğüm düzensiz. Atlama listeleri Dengeli bir düşünceye sahip değiller, bunun yerine iyi bir performans elde etme olasılığına güveniyorlar. Fibonacci ağaçları, bazen daha uzun güncellemeler karşılığında üstün asimptotik performans elde etmek için yeniden dengelemeyi erteleyerek bilerek dengeden düşer. AVL ve Kırmızı-Siyah ağaçları, derinlik-dengesi değişmezi elde etmek için her düğüme meta veri ekler.

Tüm bu yapılar ve daha fazlası, en yaygın programlama sistemlerinin (python, RAGE hariç!) Standart kütüphanelerinde mevcuttur. Bir veya ikisini uygulamak iyi bir programlama uygulamasıdır, ancak probleminiz herhangi bir hazır koleksiyon tarafından karşılanması gerekmeyen tuhaf bir performansa sahip olmadıkça, muhtemelen üretim için kendi başınıza rulo yapmak için iyi bir zaman kullanımı değildir.


4

Not 1: Herhangi bir alt ağacın yüksekliği yalnızca bir kez hesaplanır.

Not 2: Sol alt ağaç dengesizse, potansiyel olarak milyon öğe içeren sağ alt ağacın hesaplanması atlanır.

// 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

Dengeleme genellikle her yöndeki en uzun yolun uzunluğuna bağlıdır. Yukarıdaki algoritma bunu sizin için yapmayacak.

Ne uygulamaya çalışıyorsun? Çevrede kendi kendini dengeleyen ağaçlar var (AVL / Kırmızı-siyah). Aslında Java ağaçları dengelidir.



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

Bu çözümün doğru olmadığını düşünüyorum. Tek düğümü, yani kökü olan bir ağacı geçerseniz, maxDepth olarak dönecektir 1(minDepth için aynı). Doğru derinlik yine de olmalıdır. 0Bir ağacın kökü her zaman 0derinliğe sahiptir
Cratylus

3

İşte C # ile test edilmiş eksiksiz bir çözüm (üzgünüm java dev değilim) (konsol uygulamasında kopyalayıp yapıştırmanız yeterli). Dengeli tanımının değiştiğini biliyorum, bu yüzden herkes test sonuçlarımı beğenmeyebilir, ancak lütfen her bir düğümde düğüm yüksekliğini / seviyesini / derinliğini kaydetmeden ilk uyuşmazlıktan tekrarlanan bir döngüde derinliği / yüksekliği kontrol etme ve ilk uyumsuzluktan çıkma şeklindeki biraz farklı yaklaşıma bakın. (yalnızca bir işlev çağrısında sürdürmek).

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

Cevabı gönderdikten sonraki 2 dakika içinde birinin bu cevaba nasıl olumsuz oy verebileceğini anlamıyorum ?? Olumsuz oy iyidir, ancak bu çözümde neyin yanlış olduğunu açıklar mısınız?
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;
}

yorum eklemek isteyebilirsiniz
jgauffin

2

RE: @ şansın seviye-sıra geçişi yapmak için BFS kullanan çözümü.

Ağacı geçiyoruz ve bir düğümün yaprak olduğu minimum seviyeyi tanımlayan min / max-level değişkenlerine bir referans tutuyoruz.

@Lucky çözümünün bir değişiklik gerektirdiğine inanıyorum. @Codaddict tarafından önerildiği gibi, bir düğümün bir yaprak olup olmadığını kontrol etmek yerine, EITHER sol veya sağ çocukların boş olup olmadığını (her ikisini de değil) kontrol etmeliyiz. Aksi takdirde, algoritma bunu geçerli bir dengeli ağaç olarak kabul eder:

     1
    / \
   2   4
    \   \
     3   1

Python'da:

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

Bu çözüm, O (n) zamanı ve O (n) uzayında çalışan ilk soruda sağlanan tüm şartları karşılamalıdır. Bellek taşması, özyinelemeli bir çağrı yığınını şişirmek yerine yığına yönlendirilir.

Alternatif olarak, her bir kök alt ağaç için yinelemeli olarak + önbellek maksimum yüksekliklerini hesaplamak için başlangıçta ağacı gezebiliriz. Sonra başka bir yinelemeli çalıştırmada, her kök için sol ve sağ alt ağaçların önbelleğe alınmış yüksekliklerinin hiçbir zaman birden fazla farklılık gösterip göstermediğini kontrol edin. Bu aynı zamanda O (n) zamanında ve O (n) uzayda da çalışacak, ancak yığın taşmasına neden olmamak için yinelemeli olarak çalışacaktır.


1

Peki, sol ve sağ yüksekliklerini belirlemenin bir yoluna ihtiyacınız var ve eğer sol ve sağ dengeli ise.

Ve ben sadece return height(node->left) == height(node->right);

Bir heightişlev yazmakla ilgili olarak şunu okuyun: Özyinelemeyi anlama


3
Sol ve sağ yüksekliklerin mutlaka eşit değil 1 dahilinde olmasını istiyorsunuz.
Alex B

1

Ne tür bir ağaçtan bahsediyorsun? Orada kendini dengeleyen orada ağaçlar. Dengeyi korumak için ağacı yeniden düzenlemeleri gerekip gerekmediğini belirledikleri algoritmalarını kontrol edin.


1

Burada genel bir derinlik öncelikli geçişe dayalı bir versiyon. Diğer doğru cevaptan daha hızlı olmalı ve belirtilen tüm "zorlukları" ele almalıdır. Tarz için özür dilerim, Java'yı gerçekten bilmiyorum.

Hem max hem de min ayarlandıysa ve> 1 fark varsa, erken dönerek yine de çok daha hızlı hale getirebilirsiniz.

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
Güzel bir girişim ama açıkça işe yaramıyor. X bir boş düğüm olsun. Boş olmayan bir ağaç düğümünün (LEFT VALUE RIGHT) olarak gösterilmesine izin verin. Ağacı düşünün (x A (x B x)). "kök" sonsuza kadar A, B, A, B, A, B ... düğümlerini gösterir. Tekrar denemek ister misin? Bir ipucu: Ebeveyn işaretçileri olmadan aslında daha kolay .
Eric Lippert

@Eric: Oops, düzeltildi (Sanırım). Bunu O (derinlik) hafızası olmadan yapmaya çalışıyorum ve eğer yapının ana işaretçileri yoksa (çoğu zaman var), bir yığın kullanmanız gerekir.
Potatoswatter

Öyleyse bana söylüyorsunuz, O (d) geçici belleği ayırmaktan kaçınmak için ebeveyn işaretçilerinde O (n) kalıcı belleği kullanmayı tercih edersiniz, burada log n <= d <= n? Bu yanlış bir ekonomi gibi görünüyor.
Eric Lippert

Maalesef, geçişle ilgili sorunu çözmüş olsanız da, burada çok daha büyük bir sorun var. Bu, bir ağacın dengeli olup olmadığını test etmez, bir ağacın tüm yapraklarının aynı seviyeye yakın olup olmadığını test eder. Bu benim verdiğim "dengeli" nin tanımı değil. Ağacı düşünün ((((x D x) C x) B x) A x). Kodunuz, açıkça maksimum dengesiz olduğunda bunun "dengeli" olduğunu bildiriyor. Tekrar denemek ister misin?
Eric Lippert

@Eric cevap 1: Ebeveyn işaretçileri zaten başka bir şey için kullanıyorsanız yanlış bir ekonomi değil. cevap 2: tabii, neden olmasın. Bu, hata ayıklamanın tuhaf bir yolu… Körü körüne saat 4'te hiçbir şeyin
geçişlerini yazmamalıyım

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
Cevabınıza bir açıklama ve / veya kod örneğinize yorum eklemelisiniz.
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

İşte Eric'in bonus alıştırması için denediğim şey. Özyinelemeli döngülerimden gevşemeye ve dengelenemeyecek bir alt ağaç bulduğum anda geri dönmeye çalışıyorum.

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

Boş bir ağaç, yükseklik dengelidir. Boş olmayan ikili ağaç T şu durumlarda dengelenir:

1) T'nin sol alt ağacı dengelidir

2) T'nin sağ alt ağacı dengelidir

3) Sol alt ağacın ve sağ alt ağacın yükseklikleri arasındaki fark 1'den fazla değildir.

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

Zaman Karmaşıklığı: O (n)


0

Özellikle büyük ağaçlarda daha iyi bir performansa sahip olmak için, her bir düğümdeki yüksekliği kaydedebilirsiniz, böylece bu, alan Vs performansına bir değiş tokuş olur:

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

Ekleme ve silme için aynı uygulama örneği

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

Bu işe yaramaz mı?

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

Dengesiz herhangi bir ağaç her zaman bunu başaramaz.


4
Birçok dengeli ağaç da başarısız olur.
Brian
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.