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