Bagaimana cara menentukan apakah pohon biner seimbang?


113

Sudah lama sejak tahun-tahun sekolah itu. Mendapat pekerjaan sebagai spesialis IT di rumah sakit. Mencoba pindah untuk melakukan pemrograman aktual sekarang. Saya sedang mengerjakan pohon biner sekarang, dan saya bertanya-tanya apa cara terbaik untuk menentukan apakah pohon itu seimbang tinggi.

Saya sedang memikirkan sesuatu seperti ini:

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

Apakah ini implementasi yang baik? atau apakah saya melewatkan sesuatu?


Jika Anda ingin melihat pohon biner ascii Donal Fellows dengan grafik: i.imgur.com/97C27Ek.png
user7643681

1
Jawaban bagus, membantu saya masuk ke AS. (lelucon)
Henry

Jawaban:


165

Menemukan pertanyaan lama ini saat mencari sesuatu yang lain. Saya perhatikan bahwa Anda tidak pernah mendapatkan jawaban yang lengkap.

Cara untuk mengatasi masalah ini adalah memulai dengan menulis spesifikasi untuk fungsi yang Anda coba tulis.

Spesifikasi: Pohon biner yang terbentuk dengan baik dikatakan "tinggi seimbang" jika (1) kosong, atau (2) anak kiri dan kanannya seimbang tinggi dan tinggi pohon kiri dalam 1 dari ketinggian pohon yang tepat.

Sekarang setelah Anda memiliki spesifikasinya, kode itu mudah untuk ditulis. Ikuti saja spesifikasinya:

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

Menerjemahkannya ke dalam bahasa pemrograman pilihan Anda harusnya sepele.

Latihan bonus : sketsa kode naif ini melintasi pohon terlalu sering saat menghitung ketinggian. Bisakah Anda membuatnya lebih efisien?

Latihan bonus super : misalkan pohon itu besar - besaran tidak seimbang. Seperti, sejuta node jauh di satu sisi dan tiga di sisi lain. Apakah ada skenario di mana algoritma ini menghancurkan tumpukan? Dapatkah Anda memperbaiki penerapan sehingga tidak pernah merusak tumpukan, bahkan ketika diberikan pohon yang sangat tidak seimbang?

PEMBARUAN : Donal Fellows menunjukkan dalam jawabannya bahwa ada definisi berbeda dari 'seimbang' yang dapat dipilih seseorang. Misalnya, seseorang dapat mengambil definisi yang lebih ketat dari "tinggi seimbang", dan mengharuskan panjang jalur ke turunan kosong terdekat berada dalam salah satu jalur ke turunan kosong terjauh . Definisi saya tidak seketat itu, dan karenanya mengakui lebih banyak pohon.

Seseorang juga bisa kurang ketat dari definisi saya; dapat dikatakan bahwa pohon yang seimbang adalah pohon yang panjang jalur maksimumnya ke pohon kosong pada setiap cabang berbeda tidak lebih dari dua, atau tiga, atau konstanta lainnya. Atau panjang jalur maksimum adalah sebagian kecil dari panjang jalur minimum, seperti setengah atau seperempat.

Biasanya tidak masalah. Inti dari algoritma penyeimbangan pohon adalah untuk memastikan bahwa Anda tidak berakhir dalam situasi di mana Anda memiliki satu juta node di satu sisi dan tiga di sisi lain. Definisi Donal baik-baik saja secara teori, tetapi dalam praktiknya sangat merepotkan dengan algoritma penyeimbang pohon yang memenuhi tingkat keketatan itu. Penghematan kinerja biasanya tidak membenarkan biaya implementasi. Anda menghabiskan banyak waktu untuk melakukan penataan ulang pohon yang tidak perlu untuk mencapai tingkat keseimbangan yang dalam praktiknya hanya membuat sedikit perbedaan. Siapa yang peduli jika kadang-kadang dibutuhkan empat puluh cabang untuk sampai ke daun terjauh dalam satu juta simpul pohon yang tidak seimbang sempurna padahal secara teori hanya butuh dua puluh cabang dalam pohon yang seimbang sempurna? Intinya adalah bahwa tidak perlu satu juta. Beralih dari kasus terburuk satu juta ke kasus terburuk empat puluh biasanya cukup baik; Anda tidak harus menggunakan casing yang optimal.


19
+1 hanya untuk jawaban yang benar, saya tidak percaya tidak ada yang bisa menjawab ini selama 8 bulan ...
BlueRaja - Danny Pflughoeft

1
Jawaban untuk "latihan" di bawah ini…
Potatoswatter

Latihan Bonus Terjawab di bawah ini.
Brian

Jawaban sdk di bawah ini sepertinya benar dan hanya membuat 2 traversal pohon begitu juga O (n). Kecuali saya melewatkan sesuatu, apakah itu tidak menyelesaikan setidaknya pertanyaan bonus pertama Anda. Anda tentu saja juga dapat menggunakan pemrograman dinamis dan solusi Anda untuk meng-cache ketinggian menengah
Aly

Secara teoritis, saya masih harus berpihak pada definisi Donal Fellows.
Dhruv Gairola

26

Keseimbangan adalah properti yang benar-benar halus; Anda pikir Anda tahu apa itu, tetapi sangat mudah untuk salah. Secara khusus, bahkan jawaban (baik) Eric Lippert tidak aktif. Itu karena pengertian ketinggian saja tidak cukup. Anda harus memiliki konsep tinggi minimum dan maksimum sebuah pohon (di mana tinggi minimum adalah jumlah langkah paling sedikit dari akar ke daun, dan maksimumnya adalah ... yah, Anda mengerti gambarannya). Mengingat itu, kita dapat mendefinisikan keseimbangan menjadi:

Pohon yang tinggi maksimum cabang mana pun tidak lebih dari satu lebih dari tinggi minimum cabang mana pun.

(Ini sebenarnya menyiratkan bahwa cabang-cabang itu sendiri seimbang; Anda dapat memilih cabang yang sama untuk maksimum dan minimum.)

Yang perlu Anda lakukan untuk memverifikasi properti ini hanyalah pelacakan traversal pohon sederhana dari kedalaman saat ini. Pertama kali Anda mundur, itu memberi Anda kedalaman dasar. Setiap kali setelah itu ketika Anda mundur, Anda membandingkan kedalaman baru dengan baseline

  • jika sama dengan baseline, maka Anda lanjutkan saja
  • jika lebih dari satu perbedaan, pohon itu tidak seimbang
  • jika salah satu, maka Anda sekarang mengetahui kisaran keseimbangannya, dan semua kedalaman berikutnya (saat Anda akan mundur) harus berupa nilai pertama atau kedua.

Dalam kode:

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

Saya kira Anda dapat melakukan ini tanpa menggunakan pola Observer, tetapi saya merasa lebih mudah untuk bernalar seperti ini.


[EDIT]: Mengapa Anda tidak bisa hanya mengukur tinggi setiap sisi. Pertimbangkan pohon ini:

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

OK, berantakan sedikit, tapi setiap sisi akar yang seimbang: Cadalah kedalaman 2, A, B, D, Eyang kedalaman 3, dan F, G, H, Jyang mendalam 4. Ketinggian cabang kiri adalah 2 (ingat ketinggian berkurang ketika Anda melintasi cabang), tinggi cabang kanan adalah 3. Namun pohon secara keseluruhan tidak seimbang karena ada perbedaan ketinggian 2 antara Cdan F. Anda memerlukan spesifikasi minimax (meskipun algoritme sebenarnya bisa kurang rumit karena hanya boleh ada dua ketinggian yang diizinkan).


Ah, poin yang bagus. Anda bisa memiliki pohon yang h (LL) = 4, h (LR) = 3, h (RL) = 3, h (RR) = 2. Jadi, h (L) = 4 dan h (R) = 3, sehingga akan tampak seimbang dengan algoritme sebelumnya, tetapi dengan kedalaman maks / menit 4/2, ini tidak seimbang. Ini mungkin akan lebih masuk akal dengan sebuah gambar.
Tim

1
Itulah yang baru saja saya tambahkan (dengan pohon grafik ASCII paling menjijikkan di dunia).
Donal Fellows

@DonalFellows: Anda menyebutkan tinggi cabang kiri adalah 2. tetapi cabang kiri memiliki 4 simpul termasuk akar dan daun A. Tingginya akan menjadi 3 dalam hal ini benar
brain storm

22

Ini hanya menentukan jika tingkat teratas pohon seimbang. Artinya, Anda dapat memiliki pohon dengan dua cabang panjang dari paling kiri dan paling kanan, dengan tidak ada apapun di tengah, dan ini akan menjadi kenyataan. Anda perlu memeriksa secara rekursif root.leftdan root.rightuntuk melihat apakah mereka seimbang secara internal juga sebelum mengembalikan true.


Namun, jika kodenya memiliki metode ketinggian maks dan min, jika diseimbangkan secara global, ia juga akan seimbang secara lokal.
Ari

22

Respon latihan bonus. Solusi sederhana. Jelas dalam implementasi nyata seseorang mungkin membungkus ini atau sesuatu untuk menghindari keharusan pengguna untuk memasukkan tinggi dalam respon mereka.

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     

Jika pohon lebih besar dari beberapa ratus lapisan, Anda mendapatkan pengecualian stackoverflow. Anda telah melakukannya dengan efisien, tetapi tidak menangani kumpulan data ukuran sedang atau besar.
Eric Leschinski

Apakah pseudocode ini baru saja Anda buat atau apakah itu bahasa asli? (Maksud saya out heightnotasi variabel " ")
kap

@kap: Ini adalah pseudocode, tetapi sintaks keluar diambil dari C #. Pada dasarnya, ini berarti parameter bergerak dari fungsi yang dipanggil ke pemanggil (sebagai lawan dari parameter standar, yang bergerak dari pemanggil ke parameter fungsi atau ref yang dipanggil, yang bergerak dari pemanggil ke fungsi yang dipanggil dan kembali). Ini secara efektif memungkinkan fungsi untuk mengembalikan lebih dari satu nilai.
Brian

20

Solusi pasca pesanan, lintasi pohon hanya sekali. Kompleksitas waktu adalah O (n), spasi adalah O (1), lebih baik daripada solusi top-down. Saya memberi Anda implementasi versi 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
solusi yang bagus, tetapi kompleksitas ruang harus O (H) di mana H adalah tinggi pohon. Ini karena alokasi tumpukan untuk rekursi.
legrass

Apa left == -1maksudnya Kapan itu akan terjadi? Apakah kita menganggap panggilan rekursif menyiratkan bahwa left == -1benar jika semua subpohon dari anak kiri tidak seimbang?
Aspen

left == 1berarti subpohon kiri tidak seimbang, maka seluruh pohon tidak seimbang. Kami tidak perlu lagi memeriksa subtree kanan, dan dapat kembali -1.
mulai

Kompleksitas waktu adalah O (n) karena Anda harus melalui semua elemen. Dan, jika Anda memiliki x node dan perlu waktu y untuk memeriksa saldo; jika Anda memiliki 2x node, perlu waktu 2 tahun untuk memeriksa saldo. Kedengarannya benar?
Jack

Nah penjelasan dengan menggambar di sini: algoritma.tutorialhorizon.com/…
Shir

15

Definisi dari pohon biner dengan keseimbangan tinggi adalah:

Pohon biner di mana tinggi kedua subpohon dari setiap node tidak pernah berbeda lebih dari 1.

Jadi, pohon biner kosong selalu memiliki keseimbangan tinggi.
Pohon biner yang tidak kosong memiliki keseimbangan tinggi jika:

  1. Subpohon kirinya memiliki keseimbangan tinggi.
  2. Subpohon kanannya memiliki keseimbangan tinggi.
  3. Perbedaan antara tinggi subpohon kiri & kanan tidak lebih dari 1.

Pertimbangkan pohonnya:

    A
     \ 
      B
     / \
    C   D

Seperti yang terlihat, subtree kiri Aadalah tinggi-seimbang (karena kosong) dan begitu pula subtree kanannya. Tetapi tetap saja pohon tersebut tidak seimbang ketinggiannya karena kondisi 3 tidak terpenuhi karena tinggi sub-pohon kiri 0dan tinggi sub-pohon kanan adalah2 .

Juga pohon berikut tidak seimbang ketinggiannya meskipun tinggi sub-pohon kiri dan kanan sama. Kode Anda yang ada akan kembali menjadi true untuk itu.

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

Jadi kata setiap def sangat penting.

Ini akan berhasil:

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

Tautan Ideone


Jadi jawaban ini sangat membantu saya. Namun, saya menemukan [kursus pengenalan algoritma MIT] tampaknya bertentangan dengan kondisi 3. Halaman 4 menunjukkan pohon RB dimana tinggi cabang kiri adalah 2 dan cabang kanan adalah 4. Bisakah Anda memberikan penjelasan kepada saya? Mungkin saya tidak mendapatkan definisi subpohon. [1]: ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
i8abug

Perbedaan tampaknya berasal dari definisi ini di catatan kursus. Semua jalur sederhana dari setiap node x ke daun turunan memiliki jumlah yang sama dari node hitam = black-height (x)
i8abug

Sekadar menindaklanjuti, saya menemukan definisi yang mengubah poin (3) dalam jawaban Anda untuk "setiap daun 'tidak lebih dari jarak tertentu' dari akar daripada daun lainnya". Ini tampaknya memuaskan kedua kasus tersebut. Ini adalah tautan dari beberapa peralatan kursus acak
i8abug

8

Jika pohon biner seimbang atau tidak dapat diperiksa oleh Level order traversal:

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
Jawaban yang sangat bagus. Saya pikir itu memenuhi semua persyaratan yang Eric posting tentang Bonus dan Super-Bonus. Ini iteratif (menggunakan antrian) dan tidak rekursif - jadi tumpukan panggilan tidak akan meluap dan kami memindahkan semua masalah memori ke heap. Itu bahkan tidak perlu melintasi seluruh pohon. Ini bergerak tingkat demi tingkat, jadi jika pohon sangat tidak seimbang ke 1 sisi, ia akan menemukannya dengan sangat cepat (paling cepat? Lebih cepat daripada kebanyakan algoritma rekursif, meskipun Anda dapat menerapkan algoritma iteratif traversal pasca-pesanan yang akan menemukan tingkat terakhir ketidakseimbangan lebih cepat tetapi akan bertindak lebih buruk pada level pertama). Jadi +1 :-)
David Refaeli

7

Ini dibuat jauh lebih rumit dari yang sebenarnya.

Algoritmanya adalah sebagai berikut:

  1. Misalkan A = kedalaman node tingkat tertinggi
  2. Misalkan B = kedalaman node level terendah

  3. Jika abs (AB) <= 1, maka pohon tersebut seimbang


Sederhana dan lurus!
Wasim Thabraze

3
Dua masalah, itu tidak seefisien yang seharusnya, Anda membuat dua lintasan di seluruh pohon. Dan untuk pohon yang memiliki satu simpul di kiri, dan ribuan di kanan, Anda tidak perlu melangkahi semuanya, padahal Anda bisa berhenti setelah 3 pemeriksaan.
Eric Leschinski

5

Apa arti seimbang tergantung sedikit pada struktur yang ada. Misalnya, B-Tree tidak dapat memiliki node lebih dari kedalaman tertentu dari root, atau kurang dalam hal ini, semua data berada pada kedalaman tetap dari root, tetapi bisa tidak seimbang jika distribusi daun ke daun. -tetapi-satu node tidak rata. Daftar yang dilewati Sama sekali tidak memiliki gagasan tentang keseimbangan, sebaliknya mengandalkan kemungkinan untuk mencapai kinerja yang layak. Pohon Fibonacci sengaja tidak seimbang, menunda penyeimbangan kembali untuk mencapai performa asimtotik superior sebagai ganti pembaruan yang kadang-kadang lebih lama. Pohon AVL dan Red-Black melampirkan metadata ke setiap node untuk mencapai invarian keseimbangan kedalaman.

Semua struktur ini dan lainnya ada di pustaka standar sistem pemrograman yang paling umum (kecuali python, RAGE!). Menerapkan satu atau dua adalah praktik pemrograman yang baik, tetapi mungkin ini bukan penggunaan waktu yang baik untuk menggulirkan waktu Anda sendiri untuk produksi, kecuali masalah Anda memiliki beberapa kinerja khusus yang tidak perlu dipenuhi oleh koleksi off-the-shelf.


4

Catatan 1: Tinggi setiap sub-pohon dihitung hanya sekali.

Catatan 2: Jika sub-pohon kiri tidak seimbang maka perhitungan sub-pohon kanan, yang berpotensi mengandung jutaan elemen, dilewati.

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

Penyeimbangan biasanya bergantung pada panjang jalur terpanjang di setiap arah. Algoritme di atas tidak akan melakukannya untuk Anda.

Apa yang Anda coba terapkan? Ada pohon yang menyeimbangkan diri di sekitar (AVL / Merah-hitam). Padahal, pohon Jawa itu seimbang.



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

Saya rasa solusi ini tidak benar. Jika Anda melewati pohon yang memiliki satu node yaitu root, ia akan kembali sebagai maxDepth 1(sama untuk minDepth). Kedalaman yang benar seharusnya 0. Akar pohon selalu 0dalam
Cratylus

3

Berikut adalah solusi lengkap yang telah diuji di C # (maaf saya bukan java dev) (cukup salin tempel di aplikasi konsol). Saya tahu bahwa definisi keseimbangan bervariasi sehingga tidak semua orang menyukai hasil pengujian saya, tetapi harap lihat pendekatan yang sedikit berbeda untuk memeriksa kedalaman / tinggi dalam loop rekursif dan keluar pada ketidakcocokan pertama tanpa menyimpan ketinggian / level / kedalaman node pada setiap node (hanya mempertahankannya dalam panggilan fungsi).

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

Saya tidak mengerti bagaimana seseorang dapat memberikan suara negatif pada jawaban ini dalam 2 menit setelah memposting jawaban ?? Suara negatif tidak masalah, tetapi dapatkah Anda menjelaskan apa yang salah dengan solusi ini?
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;
}

Anda mungkin ingin menambahkan beberapa komentar
jgauffin

2

RE: solusi @ lucky menggunakan BFS untuk melakukan traversal tingkat-tingkat.

Kami melintasi pohon dan menyimpan referensi ke vars min / max-level yang menggambarkan level minimum di mana node adalah daun.

Saya yakin bahwa solusi @lucky memerlukan modifikasi. Seperti yang disarankan oleh @codaddict, daripada memeriksa apakah sebuah node adalah daun, kita harus memeriksa apakah salah satu anak kiri atau kanan adalah null (bukan keduanya). Jika tidak, algoritme akan menganggap ini sebagai pohon seimbang yang valid:

     1
    / \
   2   4
    \   \
     3   1

Dengan 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

Solusi ini harus memenuhi semua ketentuan yang diberikan dalam pertanyaan awal, beroperasi dalam ruang O (n) waktu dan O (n). Kelebihan memori akan diarahkan ke heap daripada meniup tumpukan panggilan rekursif.

Alternatifnya, awalnya kita bisa melintasi pohon untuk menghitung + ketinggian maksimum cache untuk setiap subpohon akar secara berulang. Kemudian dalam proses iteratif lainnya, periksa apakah ketinggian cache dari subpohon kiri dan kanan untuk setiap root tidak pernah berbeda lebih dari satu. Ini juga akan berjalan dalam O (n) waktu dan O (n) ruang tetapi secara berulang agar tidak menyebabkan stack overflow.


1

Nah, Anda membutuhkan cara untuk menentukan ketinggian kiri dan kanan, dan apakah seimbang kiri dan kanan.

Dan aku baru saja return height(node->left) == height(node->right);

Mengenai menulis heightfungsi, baca: Memahami rekursi


3
Anda ingin ketinggian kiri dan kanan berada dalam 1, tidak harus sama.
Alex B

1

Pohon jenis apa yang kamu bicarakan? Ada pohon penyeimbang diri di luar sana. Periksa algoritme mereka di mana mereka menentukan apakah mereka perlu menyusun ulang pohon untuk menjaga keseimbangan.


1

Ini adalah versi berdasarkan traversal kedalaman-pertama yang umum. Harus lebih cepat dari jawaban benar lainnya dan menangani semua "tantangan" yang disebutkan. Maaf untuk gayanya, saya tidak terlalu tahu Java.

Anda masih dapat membuatnya lebih cepat dengan kembali lebih awal jika maks dan min sama-sama ditetapkan dan memiliki perbedaan> 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
Upaya yang bagus tetapi jelas tidak berhasil. Misalkan x menjadi simpul nol. Biarkan simpul pohon bukan nol dilambangkan sebagai (NILAI KIRI KANAN). Pertimbangkan pohonnya (x A (x B x)). "root" menunjuk ke node A, B, A, B, A, B ... selamanya. Mau coba lagi? Petunjuk: sebenarnya lebih mudah tanpa petunjuk orang tua.
Eric Lippert

@ Eric: Ups, diperbaiki (saya pikir). Nah, saya mencoba melakukan ini tanpa memori O (kedalaman), dan jika struktur tidak memiliki petunjuk induk (sering kali demikian), Anda perlu menggunakan tumpukan.
Potatoswatter

Jadi apa yang Anda katakan kepada saya adalah Anda lebih suka menggunakan memori permanen O (n) di petunjuk induk untuk menghindari mengalokasikan memori sementara O (d), di mana log n <= d <= n? Ini sepertinya ekonomi palsu.
Eric Lippert

Sayangnya, meskipun Anda telah memperbaiki masalah dengan traversal, ada masalah yang jauh lebih besar di sini. Ini tidak menguji apakah sebuah pohon dalam keadaan seimbang, ini menguji apakah sebuah pohon memiliki semua daun yang mendekati level yang sama. Itu bukan definisi "seimbang" yang saya berikan. Pertimbangkan pohon ((((x D x) C x) B x) A x). Kode Anda melaporkan bahwa ini "seimbang" ketika jelas-jelas tidak seimbang secara maksimal. Mau coba lagi?
Eric Lippert

@ Eric reply 1: bukan ekonomi palsu jika Anda sudah menggunakan petunjuk orang tua untuk sesuatu yang lain. balasan 2: tentu, kenapa tidak. Ini adalah cara debugging yang aneh… Saya seharusnya tidak secara membabi buta menulis traversal apa pun pada pukul 4 pagi…
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
Anda harus menambahkan beberapa deskripsi ke jawaban dan / atau komentar Anda ke sampel kode Anda.
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

Inilah yang saya coba untuk latihan bonus Eric. Saya mencoba untuk melepas loop rekursif saya dan kembali segera setelah saya menemukan subtree tidak seimbang.

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

Pohon kosong memiliki keseimbangan tinggi. Pohon biner yang tidak kosong T diseimbangkan jika:

1) Subpohon kiri T seimbang

2) Subpohon kanan T seimbang

3) Perbedaan antara tinggi sub pohon kiri dan kanan tidak lebih dari 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;
}

Kompleksitas Waktu: O (n)


0

Untuk memiliki kinerja yang lebih baik khususnya pada pohon besar, Anda dapat menyimpan ketinggian di setiap node sehingga ini merupakan trade off space Vs kinerja:

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

Contoh penerapan penambahan dan sama untuk penghapusan

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

Bukankah ini akan berhasil?

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

Setiap pohon yang tidak seimbang akan selalu gagal dalam hal ini.


4
Banyak pohon yang seimbang akan gagal juga.
Brian
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.