Наткнулся на этот старый вопрос, ища что-то еще. Я заметил, что вы так и не получили полный ответ.
Способ решения этой проблемы - начать с написания спецификации для функции, которую вы пытаетесь написать.
Спецификация: правильно сформированное двоичное дерево называется «сбалансированным по высоте», если (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)
Перевод этого на выбранный вами язык программирования должен быть тривиальным.
Бонусное упражнение : этот эскиз наивного кода пересекает дерево слишком много раз при вычислении высот. Вы можете сделать это более эффективным?
Супер бонусное упражнение : предположим, что дерево массивно несбалансировано. Например, миллион узлов на одной стороне и три на другой. Есть сценарий, в котором этот алгоритм выдувает стек? Можете ли вы исправить реализацию так, чтобы она никогда не ударила по стеку, даже если ей дано чрезвычайно несбалансированное дерево?
ОБНОВЛЕНИЕ : Донал Феллоуз отмечает в своем ответе, что существуют различные определения «сбалансированного», которые можно выбрать. Например, можно взять более строгое определение «сбалансированной по высоте» и потребовать, чтобы длина пути до ближайшего пустого дочернего элемента находилась в пределах одного пути до самого дальнего пустого дочернего элемента. Мое определение менее строгое, и поэтому допускает больше деревьев.
Можно также быть менее строгим, чем мое определение; Можно сказать, что сбалансированное дерево - это такое, в котором максимальная длина пути к пустому дереву на каждой ветви отличается не более чем на два, или на три, или на какую-то другую константу. Или что максимальная длина пути равна некоторой доле минимальной длины пути, например, половина или четверть.
Обычно это не имеет значения. Смысл любого алгоритма балансировки дерева состоит в том, чтобы вы не оказались в ситуации, когда у вас есть миллион узлов на одной стороне и три на другой. Определение Донала хорошо в теории, но на практике это - боль, придумывающая алгоритм балансировки деревьев, который соответствует этому уровню строгости. Экономия производительности обычно не оправдывает затраты на внедрение. Вы проводите много времени, занимаясь ненужными перестановками деревьев, чтобы достичь уровня баланса, который на практике мало что меняет. Кого волнует, что иногда требуется сорок ветвей, чтобы добраться до самого дальнего листа в несовершенно сбалансированном дереве с миллионами узлов, когда теоретически может занять только двадцать в идеально сбалансированном дереве? Дело в том, что это никогда не займет миллион. Переход от худшего случая в миллион к худшему из сорока обычно достаточно хорош; Вам не нужно идти до оптимального случая.