Рис. 4.6. Листья этого дерева представляют современные языки
Двоичное дерево поиска
Рис. 4.7. Пример двоичного дерева поиска
Если дерево соблюдает это свойство, в нем легко отыскать узел с заданным ключом/значением:
function find_node(binary_tree, value)
node ← binary_tree.root_node
····while node
········if node.value = value
············return node
········if value > node.value
············node ← node.right
········else
············node ← node.left
····return "NOT FOUND"
Чтобы вставить элемент, находим последний узел, следуя правилам построения дерева поиска, и подключаем к нему новый узел справа или слева:
function insert_node(binary_tree, new_node)
····node ← binary_tree.root_node
····while node
········last_node ← node
········if new_node.value > node.value
············node ← node.right
········else
············node ← node.left
····if new_node.value > last_node.value
········last_node.right ← new_node
····else
········last_node.left ← new_node
Балансировка дерева. Если вставить в двоичное дерево поиска слишком много узлов, в итоге получится очень высокое дерево, где большинство узлов имеют всего один дочерний узел. Например, если последовательно вставлять узлы с ключами/значениями, которые всегда больше предыдущих, в итоге получится нечто, похожее на связный список. Однако мы можем перестроить узлы в дереве так, что его высота уменьшится. Эта процедура вызывается
Рис. 4.8. Одно и то же двоичное дерево поиска с разной балансировкой: сбалансированное плохо, средне и идеально
Большинство операций с деревом требует обхода узлов по ссылкам, пока не будет найден конкретный узел. Чем больше высота дерева, тем длиннее средний путь между узлами и тем чаще приходится обращаться к памяти. Поэтому важно уменьшать высоту деревьев. Идеально сбалансированное двоичное дерево поиска можно создать из сортированного списка узлов следующим образом:
function build_balanced(nodes)
····if nodes is empty
········return NULL
····middle ← nodes.length/2
····left ← nodes.slice(0, middle — 1)
····right ← nodes.slice(middle + 1, nodes.length)
····balanced ← BinaryTree.new(root=nodes[middle])
····balanced.left ← build_balanced(left)
····balanced.right ← build_balanced(right)
····return balanced
Рассмотрим двоичное дерево поиска с
Однако балансировка дерева — дорогостоящая операция, поскольку требует сортировки всех узлов. Если делать балансировку после каждой вставки или удаления, операции станут значительно медленнее. Обычно деревья подвергаются этой процедуре после нескольких вставок и удалений. Но балансировка от случая к случаю является разумной стратегией только в отношении редко изменяемых деревьев.