Як визначити, чи врівноважене двійкове дерево?


113

Минув час із тих шкільних років. Влаштувався на роботу в ІТ-спеціаліст в лікарні. Намагаємось перейти, щоб зараз зробити якесь фактичне програмування. Зараз я працюю над бінарними деревами, і мені було цікаво, що було б найкращим способом визначити, чи дерево збалансоване по висоті.

Я щось над цим думав:

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 Donal Fellows з графікою: i.imgur.com/97C27Ek.png
user7643681

1
Гарна відповідь, допомогла мені потрапити до США. (жарти)
Генріх

Відповіді:


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)

Переклад цього на мову програмування на ваш вибір повинен бути тривіальним.

Бонусні вправи : цей наївний ескіз коду занадто багато разів обходить дерево, обчислюючи висоти. Чи можете ви зробити це більш ефективним?

Супер бонусна вправа : припустимо, дерево масово врівноважене. Мовляв, мільйон вузлів глибоко з одного боку і три глибокі з іншого. Чи існує сценарій, при якому цей алгоритм видуває стек? Чи можете ви зафіксувати реалізацію таким чином, щоб вона ніколи не забивала стек, навіть коли деревце масово не врівноважене?

ОНОВЛЕННЯ : Доналісти стипендіати вказують у своїй відповіді, що існують різні визначення поняття "збалансований", який можна вибрати. Наприклад, можна взяти суворіше визначення «зрівноваженого по висоті» і вимагати, щоб довжина шляху до найближчої порожньої дитини була в межах одного зі шляху до найдальшої порожньої дитини. Моє визначення менш суворе, ніж це, і тому допускає більше дерев.

Можна також бути менш суворим, ніж моє визначення; можна сказати, що врівноважене дерево - це те, в якому максимальна довжина шляху до порожнього дерева на кожній гілці відрізняється не більше ніж на два, три, або якусь іншу константу. Або що максимальна довжина шляху - це якась частка мінімальної довжини шляху, як півтора чи чверть.

Зазвичай це не має значення. Суть будь-якого алгоритму балансування дерев полягає в тому, щоб ви не закінчилися в ситуації, коли у вас мільйон вузлів з одного боку і три з іншого. Визначення Доналя теоретично добре, але на практиці це біль, що створює алгоритм балансування дерев, який відповідає такому рівню суворості. Економія ефективності зазвичай не виправдовує витрат на впровадження. Ви витрачаєте багато часу, роблячи непотрібні перестановки дерев, щоб досягти рівня рівноваги, який на практиці мало значення. Кому байдуже, якщо іноді потрібно сорок гілок, щоб дістатися до найдальшого листя в мільйоні вузлів недосконало врівноваженого дерева, коли теоретично в ідеально збалансованому дереві воно може зайняти лише двадцять? Справа в тому, що це ніколи не займає мільйон. Перехід від найгіршого випадку від мільйона до гіршого за сорок - це зазвичай досить добре; вам не доведеться пройти весь шлях до оптимального випадку.


19
+1 за єдино правильну відповідь, я не можу повірити, що ніхто не зміг відповісти на це протягом 8 місяців ...
BlueRaja - Danny Pflughoeft

1
Відповідь на "вправи" нижче ...
Potatoswatter

Бонусна вправа відповіла нижче.
Брайан

Відповідь sdk нижче здається правильною і робить лише 2 обходи дерев, так це O (n). Якщо я не пропускаю щось, чи це не вирішує принаймні вашого першого бонусного питання. Ви , звичайно , можете також використовувати динамічне програмування і ваше рішення кешування проміжних висот
Aly

Теоретично я все одно повинен був би визначитися з визначенням стипендіатів.
Друв Гайрола

26

Баланс - це справді тонка властивість; ти думаєш, що знаєш, що це таке, але так легко помилитися. Зокрема, навіть (хороша) відповідь Еріка Ліпперта відключена. Це тому, що поняття висоти недостатньо. Потрібно мати поняття мінімальної та максимальної висоти дерева (де мінімальна висота - найменша кількість кроків від кореня до листа, а максимальна - ну, ви отримаєте малюнок). Враховуючи це, ми можемо визначити баланс таким:

Дерево, де максимальна висота будь-якої гілки не більше однієї, ніж мінімальна висота будь-якої гілки.

(Це фактично означає, що гілки самі збалансовані; ви можете вибрати одну і ту ж гілку як для максимуму, так і для мінімального.)

Все, що вам потрібно зробити, щоб перевірити цю властивість - це просте обхід дерева, що відстежує поточну глибину. Перший раз, коли ви повертаєтесь назад, це дає базову глибину. Кожен раз після цього, коли ви повертаєтесь назад, ви порівнюєте нову глибину з базовою лінією

  • якщо вона дорівнює базовій лінії, то ви просто продовжуєте
  • якщо воно більше ніж одне, дерево не врівноважено
  • якщо це одноразово, то тепер ви знаєте діапазон балансу, і всі наступні глибини (коли ви збираєтеся відступити) повинні бути або першим, або другим значенням.

У коді:

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

Я припускаю, що ви могли це зробити без використання шаблону спостерігача, але мені простіше міркувати таким чином.


[EDIT]: Чому ви не можете просто взяти висоту кожної сторони. Розглянемо це дерево:

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

Добре, трохи сумбурно, але кожна сторона кореня збалансований: Cце глибина 2, A, B, D, Eє глибина 3, і F, G, H, Jє глибина 4. Висота лівій гілці 2 (пам'ятаєте , висота зменшується , як ви траверси гілка), висота правої гілки - 3. Проте дерево загалом не врівноважене, оскільки різниця у висоті 2 між Cі F. Вам потрібна специфікація minimax (хоча власне алгоритм може бути менш складним, оскільки має бути лише дві дозволені висоти).


Ах, хороший пункт. У вас може бути дерево, яке h (LL) = 4, h (LR) = 3, h (RL) = 3, h (RR) = 2. Таким чином, h (L) = 4 і h (R) = 3, тому, здавалося б, врівноважено попередній алгоритм, але з глибиною max / min 4/2 це не врівноважено. Це, мабуть, має більше сенсу з малюнком.
Тім

1
Це те, що я щойно додав (з найгіршим графічним деревом світу ASCII).
Стипендіати Доналу

@DonalFellows: Ви згадали висоту лівої гілки 2, але на лівій гілці є 4 вузли, включаючи корінь та лист А. Висота буде 3 у цьому випадку правильною
мозкова буря

22

Це визначає лише, чи верхній рівень дерева збалансований. Тобто у вас може бути дерево з двома довгими гілками вкрай ліворуч і вкрай праворуч, без нічого посередині, і це повернеться правдою. Вам потрібно рекурсивно перевірити root.leftі root.rightперевірити, чи вони також внутрішньо врівноважені, перш ніж повернути справжнє.


Однак, якби код мав метод максимальної та мінімальної висоти, якщо він буде глобально збалансованим, він би також був локально врівноваженим.
Арі

22

Бонусна відповідь на вправу. Просте рішення. Очевидно, що в реальній реалізації можна обгорнути це чи щось, щоб уникнути необхідності користувачеві включати висоту у свою відповідь.

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

Якщо дерево більше декількох сотень шарів у висоту, ви отримуєте виняток stackoverflow. Ви зробили це ефективно, але він не обробляє набори даних середнього або великого розміру.
Ерік Лещинський

Це псевдокод, який ви тільки що придумали, чи це справжня мова? (Я маю на увазі " out height" змінну позначення)
кап

@kap: Це псевдокод, але вихідний синтаксис взято з C #. В основному, це означає, що параметр переходить від викликаної функції до абонента (на відміну від стандартних параметрів, які переходять від абонента до викликаної функції або параметрів ref, які переходять від абонента до викликаної функції і назад). Це ефективно дозволяє функціям повертати більше одного значення.
Брайан

20

Розмістіть замовлення, обведіть дерево лише один раз. Часова складність - O (n), простір - O (1), це краще, ніж рішення зверху вниз. Я даю вам реалізацію версії java.

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

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

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

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

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

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

4
приємне рішення, але складність простору повинна бути O (H), де H - висота дерева. Це тому, що розподіл стеків для рекурсії.
леграс

Що left == -1означає? Коли це буде колись? Чи вважаємо, що рекурсивний виклик означає, що left == -1це правда, якщо всі підряди лівих дітей не врівноважені?
Аспен

left == 1означає, що ліве піддерев’я є неврівноваженим, тоді все дерево неврівноважене. Нам більше не потрібно перевіряти правильне піддерево і можемо повернутися -1.
Теннінг

Часова складність становить O (n), оскільки вам доведеться пройти всі елементи. І якщо у вас були x вузли, і вам знадобиться час, щоб перевірити баланс; якщо у вас було 2x вузли, знадобиться 2 роки, щоб перевірити баланс. Це все звучить правильно?
Джек

Ну пояснення з малюнком є ​​тут: алгоритми.tutorialhorizon.com/…
Шир

15

Визначення бінарного дерева з урівноваженою висотою:

Бінарне дерево, у якому висота двох підкреслень кожного вузла ніколи не відрізняється більш ніж на 1.

Отже, порожнє двійкове дерево завжди врівноважене по висоті.
Непорожнє двійкове дерево збалансоване по висоті, якщо:

  1. Її ліве піддерево вирівняне по висоті.
  2. Його праве піддерево врівноважене по висоті.
  3. Різниця між висотами лівого та правого піддерева не більша за 1.

Розглянемо дерево:

    A
     \ 
      B
     / \
    C   D

Як видно, ліве піддерево Aмає балансування по висоті (як і порожнє), так і його праве піддерево. Але все-таки дерево не врівноважене по висоті, оскільки умова 3 не виконується, оскільки висота лівого піддерева є, 0а висота правого піддерева -2 .

Крім того, наступне дерево не збалансоване по висоті, навіть якщо висота лівого та правого піддерева однакова. Ваш існуючий код поверне справжнє для нього.

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

Тож слово кожен у деф є дуже важливим.

Це спрацює:

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

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

Ideone Link


Тож ця відповідь мені дуже допомогла. Однак я знайшов, що вільний [курс введення в алгоритми MIT], здається, суперечить умові 3. Сторінка 4 показує дерево RB, де висота лівої гілки 2, а правої гілки - 4. Чи можете ви запропонувати мені уточнення? Можливо, я не отримую визначення піддерева. [1]: ocw.mit.edu/courses/electrical-engineering-and-computer-science/…
i8abug

Здається, різниця випливає з цього визначення в конспектах курсу. Усі прості шляхи від будь-якого вузла x до листя нащадка мають однакову кількість чорних вузлів = black-height (x)
i8abug

Для подальшого дослідження я знайшов визначення, яке змінює пункт (3) у вашій відповіді на "кожен листок" не більше певної відстані "від кореня, ніж будь-який інший лист". Це, мабуть, задовольняє обидва випадки. Ось посилання з якогось випадкового посуду курсу
i8abug

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 :-)
Девід Рефаелі

7

Це робиться набагато складніше, ніж є насправді.

Алгоритм такий:

  1. Нехай A = глибина вузла найвищого рівня
  2. Нехай B = глибина вузла найнижчого рівня

  3. Якщо abs (AB) <= 1, то дерево врівноважено


Просто і прямо!
Васим Табразе

3
Дві проблеми, це не так ефективно, як могло б, ви робите два проходи по всьому дереву. А для дерев, у яких один вузол зліва та тисячі праворуч, ви без необхідності переходите через всю справу, коли ви могли зупинитися після 3 перевірок.
Ерік Лещинський

5

Які збалансовані засоби дещо залежать від структури, що знаходиться під рукою. Наприклад, дерево B не може мати вузли, що перевищують певну глибину від кореня, або меншу для цього питання, всі дані живуть на фіксованій глибині від кореня, але це може бути поза балансом, якщо розподіл листя на листя -але один вузол нерівномірний. Пропускні списки Не мають уявлення про баланс, покладаючись на ймовірність досягнення гідних показників. Дерева Фібоначчі цілеспрямовано випадають з рівноваги, відкладаючи ребаланс для досягнення найкращих асимптотичних показників в обмін на періодично довші оновлення. AVL та Червоно-Чорні дерева прикріплюють метадані до кожного вузла для досягнення інваріантного балансу глибини.

Усі ці структури і багато іншого є в стандартних бібліотеках найбільш поширених систем програмування (крім python, RAGE!). Реалізація одного чи двох є хорошою практикою програмування, але, мабуть, це не вдале використання часу для виготовлення власних виробництв, якщо тільки ваша проблема не має особливих продуктивних потреб, які не задовольняються жодними нестандартними колекціями.


4

Примітка 1: Висота будь-якого піддерева обчислюється лише один раз.

Примітка 2: Якщо ліве піддерево не врівноважене, то обчислення правого піддерева, потенційно містить мільйон елементів, пропускається.

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

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

3

Балансування зазвичай залежить від довжини найдовшого шляху в кожному напрямку. Наведений вище алгоритм не збирається цього робити для вас.

Що ви намагаєтесь реалізувати? Навколо є самоврівноважуючі дерева (AVL / Червоно-чорні). Насправді дерева Java врівноважені.



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

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

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

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

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

Я думаю, що це рішення не є правильним. Якщо ви передасте дерево, яке має єдиний вузол, тобто корінь, воно повернеться як maxDepth 1(те саме для minDepth). Хоча має бути правильна глибина 0. Корінь дерева завжди має 0глибину
Cratylus

3

Ось повне розроблене перевірене рішення в C # (вибачте, що я не java dev) (просто скопіюйте пасту в консольний додаток). Я знаю, що визначення збалансованого змінюється, тому не всім можуть сподобатися мої результати тестування, але, будь ласка, просто подивіться на дещо інший підхід перевірки глибини / висоти в рекурсивному циклі та виходу на перше невідповідність, не зберігаючи висоту / рівень / глибину вузла на кожному вузлі (лише підтримуючи його у виклику функції).

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

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

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

            Console.ReadKey();
        }

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

            var tree = new BinarySearchTree();

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

            Console.WriteLine(tree.IsBalanced());

            var sb = tree.GetBreadthWardsTraversedNodes();

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

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

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

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

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

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

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

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

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

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


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

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

Я не розумію, як хтось міг негативно проголосувати цю відповідь протягом 2 хвилин після публікації відповіді ?? Негативне голосування нормально, але чи можете ви поясніть, що не так у цьому рішенні?
sbp

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

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

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

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

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

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

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

        if ( level )
        {
            ++node_count;
        }

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

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

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

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

    return true;
}

int main()
{
    node tree[15];

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

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

    return 0;
}

ви можете додати коментарі
jgauffin

2

RE: @ рішення щасливця, що використовує BFS, щоб пройти рівне замовлення.

Ми обходимо дерево та зберігаємо посилання на vars min / max-level, який описує мінімальний рівень, на якому вузол є листочком.

Я вважаю, що рішення @lucky потребує модифікації. Як запропонував @codaddict, замість того, щоб перевірити, чи вузол є листочком, ми повинні перевірити, чи ВСЕ ліві чи праві діти не мають нуля (не обидва). В іншому випадку алгоритм вважатиме це правильним збалансованим деревом:

     1
    / \
   2   4
    \   \
     3   1

На Python:

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

    import queue

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

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

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

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

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

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

        level += 1

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

    return True

Це рішення повинно відповідати всім умовам, викладеним у початковому питанні, що діють в O (n) час та O (n) простір. Переповнення пам’яті спрямовуватиметься до купи, а не продукувати рекурсивний стек викликів.

Крім того, ми могли б спочатку перетинати дерево, щоб обчислити + кеш-максимум висот для кожного кореневого піддерева ітеративно. Потім в іншому ітераційному виконанні перевірте, чи ніколи кешовані висоти лівого і правого підкреслень для кожного кореня ніколи не відрізняються більш ніж на одиницю. Це також запускається в O (n) час та O (n) простір, але ітеративно, щоб не викликати переповнення стека.


1

Ну, вам потрібен спосіб визначити висоту зліва і справа, і якщо зліва і справа врівноважені.

І я просто return height(node->left) == height(node->right);

Щодо написання heightфункції прочитайте: Розуміння рекурсії


3
Ви хочете, щоб ліва і права висоти були в межах 1, не обов'язково рівні.
Алекс Б

1

Про яке дерево ви говорите? Там є самоврівноважуючі дерева. Перевірте їхні алгоритми, де вони визначають, чи потрібно їх впорядкувати дерево, щоб зберегти рівновагу.


1

Ось версія, що ґрунтується на загальній обхідній глибині. Повинен бути швидшим, ніж інша правильна відповідь і впоратися з усіма згаданими «викликами». Вибачте за стиль, я не дуже знаю Java.

Ви все ще можете зробити це набагато швидше, повернувшись достроково, якщо макс і хв встановлені і мають різницю> 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 - нульовий вузол. Нехай ненульовий вузол дерева позначається як (ЛІВНІ ЗНАЧЕННІ ПРАВА). Розглянемо дерево (x A (x B x)). "root" вказує на вузли A, B, A, B, A, B ... назавжди. Хочете спробувати ще раз? Підказка: без батьківських вказівників насправді простіше .
Ерік Ліпперт

@Eric: На жаль, виправлено (я думаю). Ну, я намагаюся зробити це без пам'яті O (глибина), і якщо в структурі немає батьківських вказівників (це часто є), вам потрібно використовувати стек.
Potatoswatter

Отже, що ви мені говорите, ви краще використовувати O (n) постійну пам'ять у батьківських вказівниках, щоб уникнути виділення O (d) тимчасової пам'яті, де log n <= d <= n? Це здається помилковою економією.
Ерік Ліпперт

На жаль, хоча ви вирішили проблему з проїздом, тут є набагато більша проблема. Це не перевіряє, чи є дерево збалансованим, воно перевіряє, чи є у дерева всі його листя близькі до одного рівня. Це не визначення "збалансованого", яке я дав. Розглянемо дерево ((((x D x) C x) B x) A x). Ваш код повідомляє, що це "збалансовано", коли воно, очевидно, є максимально незбалансованим. Хочете спробувати ще раз?
Ерік Ліпперт

@Eric відповідь 1: неправдива економія, якщо ви вже використовуєте батьківські вказівники для чогось іншого. відповідь 2: впевнений, чому ні. Це химерний спосіб налагодження ... Я не повинен сліпо писати будь-які
описи

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

Щоб мати кращі показники особливо на величезних деревах, ви можете зберегти висоту в кожному вузлі, так що це компроміс від простору Vs:

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
Багато збалансованих дерев теж не вдасться.
Брайан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.