Как определить, сбалансировано ли двоичное дерево?


114

Давно прошли те школьные годы. Получил работу айтишником в больнице. Сейчас пытаюсь заняться программированием. Сейчас я работаю над бинарными деревьями, и мне было интересно, как лучше всего определить, сбалансировано ли дерево по высоте.

Я думал об этом:

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 01

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)

Перевод этого на язык программирования по вашему выбору должен быть тривиальным.

Дополнительное упражнение : этот наивный набросок кода слишком много раз пересекает дерево при вычислении высот. Можете ли вы сделать его более эффективным?

Супер бонусное упражнение : предположим, что дерево сильно разбалансировано. Например, миллион узлов глубиной с одной стороны и три глубоких - с другой. Есть ли сценарий, при котором этот алгоритм взрывает стек? Можете ли вы исправить эту реализацию, чтобы она никогда не взорвала стек, даже если дано сильно несбалансированное дерево?

ОБНОВЛЕНИЕ : Donal Fellows отмечает в своем ответе, что есть разные определения «сбалансированного», которые можно выбрать. Например, можно было бы взять более строгое определение «сбалансированной по высоте» и потребовать, чтобы длина пути до ближайшего пустого дочернего элемента находилась в пределах одного из путей к самому дальнему пустому дочернему элементу . Мое определение менее строгое, поэтому допускает больше деревьев.

Можно также быть менее строгим, чем мое определение; можно сказать, что сбалансированное дерево - это такое дерево, в котором максимальная длина пути к пустому дереву на каждой ветви отличается не более чем на два, три или некоторую другую константу. Или что максимальная длина пути составляет некоторую часть минимальной длины пути, например, половину или четверть.

Обычно это не имеет значения. Смысл любого алгоритма балансировки дерева состоит в том, чтобы гарантировать, что вы не попадете в ситуацию, когда у вас есть миллион узлов с одной стороны и три с другой. Определение Донала прекрасно в теории, но на практике сложно найти алгоритм балансировки дерева, который соответствует этому уровню строгости. Снижение производительности обычно не оправдывает затрат на внедрение. Вы тратите много времени на ненужную перестановку деревьев, чтобы достичь уровня баланса, который на практике не имеет большого значения. Кого волнует, что иногда требуется сорок ветвей, чтобы добраться до самого дальнего листа в несовершенно сбалансированном дереве с миллионами узлов, когда теоретически может потребоваться только двадцать в идеально сбалансированном дереве? Дело в том, что для этого никогда не нужно миллион. Обычно достаточно перейти от наихудшего случая в миллион к худшему случаю - сорок; вам не нужно полностью переходить к оптимальному случаю.


19
+1 за единственно правильный ответ, я не могу поверить, что никто не мог ответить на этот вопрос в течение 8 месяцев ...
BlueRaja - Дэнни Пфлугофт

1
Ответ на «упражнения» ниже…
Potatoswatter

Ответил на бонусное упражнение ниже.
Брайан

Ответ sdk ниже кажется правильным и выполняет только 2 обхода дерева, поэтому O (n). Если я что-то не упустил, разве это не решит по крайней мере ваш первый вопрос о бонусе. Вы, конечно, также можете использовать динамическое программирование и ваше решение для кеширования промежуточных высот
Али

Теоретически я бы все же согласился с определением Donal Fellows.
Дхрув Гайрола,

27

Баланс - действительно тонкое свойство; вы думаете, что знаете, что это такое, но так легко ошибиться. В частности, даже (хороший) ответ Эрика Липперта неверен. Это потому, что понятия высоты недостаточно. Вам необходимо иметь представление о минимальной и максимальной высоте дерева (где минимальная высота - это наименьшее количество шагов от корня до листа, а максимальное - это ... ну, вы понимаете). Учитывая это, мы можем определить баланс как:

Дерево, у которого максимальная высота любой ветви не более чем на единицу больше минимальной высоты любой ветви.

(На самом деле это означает, что ветви сами сбалансированы; вы можете выбрать одну и ту же ветвь как для максимума, так и для минимума.)

Все, что вам нужно сделать, чтобы проверить это свойство, - это простой обход дерева с отслеживанием текущей глубины. Когда вы в первый раз возвращаетесь назад, это дает вам базовую глубину. Каждый раз после этого, когда вы возвращаетесь назад, вы сравниваете новую глубину с базовой линией.

  • если он равен базовой линии, вы просто продолжаете
  • если их несколько, дерево не сбалансировано
  • если это один раз, то теперь вы знаете диапазон баланса, и все последующие глубины (когда вы собираетесь вернуться) должны быть либо первым, либо вторым значением.

В коде:

class Tree {
    Tree left, right;
    static interface Observer {
        public void before();
        public void after();
        public boolean end();
    }
    static boolean traverse(Tree t, Observer o) {
        if (t == null) {
            return o.end();
        } else {
            o.before();
            try {
                if (traverse(left, o))
                    return traverse(right, o);
                return false;
            } finally {
                o.after();
            }
        }
    }
    boolean balanced() {
        final Integer[] heights = new Integer[2];
        return traverse(this, new Observer() {
            int h;
            public void before() { h++; }
            public void after() { h--; }
            public boolean end() {
                if (heights[0] == null) {
                    heights[0] = h;
                } else if (Math.abs(heights[0] - h) > 1) {
                    return false;
                } else if (heights[0] != h) {
                    if (heights[1] == null) {
                        heights[1] = h;
                    } else if (heights[1] != h) {
                        return false;
                    }
                }
                return true;
            }
        });
    }
}

Я полагаю, вы могли бы сделать это без использования паттерна Observer, но мне легче рассуждать таким образом.


[РЕДАКТИРОВАТЬ]: Почему нельзя просто взять высоту каждой стороны. Рассмотрим это дерево:

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

Хорошо, немного сумбурно, но каждая сторона корня сбалансирован: Cэто глубина 2, A, B, D, Eявляются глубина 3, и F, G, H, Jявляются глубина 4. Высота левой ветви 2 (помните , высота уменьшается , как вы траверсы ветвь), высота правой ветви равна 3. Тем не менее, дерево в целом не сбалансировано, поскольку разница в высоте между Cи составляет 2 F. Вам нужна минимаксная спецификация (хотя фактический алгоритм может быть менее сложным, поскольку должно быть только две разрешенные высоты).


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

1
Это то, что я только что добавил (с самым отвратительным графическим деревом ASCII в мире).
Donal Fellows

@DonalFellows: вы упомянули, что высота левой ветви равна 2. но левая ветвь имеет 4 узла, включая корень и лист A. В этом случае высота будет 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 - высота дерева. Это связано с выделением стека для рекурсии.
legrass

Что left == -1значит? Когда такое случится? Предполагаем ли мы, что рекурсивный вызов подразумевает, что left == -1это правда, если все поддеревья левых дочерних элементов неуравновешены?
Aspen

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

Сложность по времени - O (n), потому что вам нужно пройти через все элементы. И, если у вас было x узлов, и проверка баланса займет y времени; если у вас было 2x узла, проверка баланса займет 2 года. Все в порядке?
Джек

Хорошо объяснение с рисунком здесь: алгоритмы.tutorialhorizon.com/…
Шир

15

Определение бинарного дерева со сбалансированной высотой:

Двоичное дерево, в котором высота двух поддеревьев каждого узла никогда не отличается более чем на 1.

Итак, пустое двоичное дерево всегда сбалансировано по высоте.
Непустое двоичное дерево сбалансировано по высоте, если:

  1. Его левое поддерево сбалансировано по высоте.
  2. Его правое поддерево сбалансировано по высоте.
  3. Разница между высотами левого и правого поддерева не больше 1.

Рассмотрим дерево:

    A
     \ 
      B
     / \
    C   D

Как видно, левое поддерево Aсбалансировано по высоте (так как оно пусто), как и его правое поддерево. Но все же дерево не сбалансировано по высоте, так как условие 3 не выполняется, как высота левого поддерева 0и высота правого поддерева 2.

Также следующее дерево не сбалансировано по высоте, хотя высота левого и правого поддерева равны. Ваш существующий код вернет true для этого.

       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


Так что этот ответ мне очень помог. Тем не менее, я обнаружил, что бесплатный [Введение в курс алгоритмов 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, то дерево сбалансировано


Просто и прямо!
Wasim Thabraze

3
Две проблемы, это не так эффективно, как могло бы быть, вы делаете два прохода по всему дереву. А для деревьев, у которых есть один узел слева и тысячи справа, вы без необходимости проходите через все это, когда вы могли бы остановиться после трех проверок.
Эрик Лещински

5

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

Все эти и многие другие структуры присутствуют в стандартных библиотеках наиболее распространенных систем программирования (кроме python, RAGE!). Внедрение одного или двух - это хорошая практика программирования, но, вероятно, не лучшее использование времени для создания собственного продукта, если только ваша проблема не связана с какой-то особенной производительностью, не удовлетворяемой никакими готовыми коллекциями.


4

Примечание 1. Высота любого поддерева вычисляется только один раз.

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

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

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

3

Балансировка обычно зависит от длины самого длинного пути в каждом направлении. Вышеупомянутый алгоритм не сделает этого за вас.

Что вы пытаетесь реализовать? Вокруг есть самобалансирующиеся деревья (АВЛ / Красно-черный). На самом деле деревья 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глубина
Кратил

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: @ lucky с использованием BFS для обхода порядка уровней.

Мы проходим по дереву и сохраняем ссылку на переменные 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, а не обязательно равной.
Alex B

1

О каком дереве вы говорите? Есть самобалансирующиеся деревья. Проверьте их алгоритмы, где они определяют, нужно ли им переупорядочивать дерево для поддержания баланса.


1

Вот версия, основанная на общем обходе в глубину. Должен быть быстрее, чем другой правильный ответ, и справляться со всеми упомянутыми «проблемами». Извиняюсь за стиль, я вообще не знаю Java.

Вы все равно можете сделать это намного быстрее, вернувшись раньше, если установлены оба параметра max и min и имеют разницу> 1.

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

1
Хорошая попытка, но явно не работает. Пусть x будет нулевым узлом. Пусть ненулевой узел дерева обозначается как (ЛЕВОЕ ЗНАЧЕНИЕ ПРАВО). Рассмотрим дерево (x A (x B x)). «корень» указывает на узлы 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: конечно, а почему бы и нет. Это странный способ отладки… Я не должен слепо писать обход чего-либо в 4 часа утра…
Potatoswatter

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


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

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

1
Вы должны добавить описание к вашему ответу и / или комментарии к вашему образцу кода.
Брэд Кэмпбелл,

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

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

public class BinaryTree {

    Node root;

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

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

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

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

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

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

        return false;
    }

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


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

0

Вот что я пробовал для бонусного упражнения Эрика. Я пытаюсь размотать свои рекурсивные циклы и вернуться, как только обнаруживаю, что поддерево не сбалансировано.

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

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

    int lb = 0, rb = 0;

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

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

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

    rb = heightBalancedRecursive(root -> right,i)

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

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

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

}}
public boolean balanced(Node n){

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

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

0

Пустое дерево сбалансировано по высоте. Непустое двоичное дерево T сбалансировано, если:

1) Левое поддерево T сбалансировано

2) Правое поддерево T сбалансировано

3) Разница между высотами левого поддерева и правого поддерева не более 1.

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

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

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

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

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

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

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

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

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


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

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

    return(node);
}

int main()
{
  int height = 0;

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

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

  getchar();
  return 0;
}

Сложность времени: O (n)


0

Чтобы получить лучшую производительность, особенно на огромных деревьях, вы можете сохранить высоту в каждом узле, так что это компромисс между пространством и производительностью:

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

Пример реализации добавления и того же для удаления

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

     }

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

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

}

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

  return true;
}

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

  return false;

}

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

-2

Разве это не сработает?

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

Любое несбалансированное дерево всегда терпит неудачу.


4
Многие сбалансированные деревья тоже не справятся.
Брайан
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.