Дерево поиска, наивная реализация — различия между версиями
Shersh (обсуждение | вклад) (→Операции в бинарном дереве поиска) |
|||
| Строка 86: | Строка 86: | ||
Обе операции выполняются за время <tex>O(h)</tex>. | Обе операции выполняются за время <tex>O(h)</tex>. | ||
====Реализация без использования информации о родителе==== | ====Реализация без использования информации о родителе==== | ||
| − | + | Рассмотрим поиск следующего элемента для некоторого ключа <tex>x</tex>. Поиск будем начинать с корня дерева, храня текущий узел <tex>current</tex> и узел <tex>parent</tex> - последний посещенный узел, ключ которого больше <tex>x</tex>. Спускаемся вниз по дереву, как в алгоритме поиска узла, обновляя <tex>parent</tex> при переходе к левому потомку (если мы перешли к правому потомку, значит, что ключ в предыдущем узле меньше <tex>x</tex>). Таким образом, мы обойдем всё дерево и найдем элемент с минимальным ключем, большем <tex>x</tex>. Аналогично реализуется операция поиска предыдущего элемента. | |
| − | '''Node''' next( | + | '''Node''' next(x : '''T'''): |
| − | + | '''Node''' current = root, parent = ''null'' <font color="green">// root {{---}} корень дерева</font> | |
| − | + | '''while''' current != ''null'' | |
| − | + | '''if''' current.key > x | |
| − | + | parent = current | |
| − | + | current = current.left | |
| − | + | '''else''' | |
| − | + | current = current.right | |
| − | + | '''return''' parent | |
| − | + | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
=== Вставка === | === Вставка === | ||
Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент. | Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент. | ||
====Реализация с использованием информации о родителе==== | ====Реализация с использованием информации о родителе==== | ||
| − | '''func''' insert(x : '''Node''', z : ''' | + | '''func''' insert(x : '''Node''', z : '''T'''): <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый ключ</font> |
| − | '''if''' z | + | '''if''' z > x.key |
'''if''' x.right != ''null'' | '''if''' x.right != ''null'' | ||
insert(x.right, z) | insert(x.right, z) | ||
'''else''' | '''else''' | ||
| − | + | '''Node''' y | |
| − | x.right = | + | y.parent = x |
| − | '''else''' | + | y.key = z |
| + | x.right = y | ||
| + | '''else if''' z < x.key | ||
'''if''' x.left != ''null'' | '''if''' x.left != ''null'' | ||
insert(x.left, z) | insert(x.left, z) | ||
'''else''' | '''else''' | ||
| − | + | '''Node''' y | |
| − | x.left = | + | y.parent = x |
| + | y.key = z | ||
| + | x.left = y | ||
====Реализация без использования информации о родителе==== | ====Реализация без использования информации о родителе==== | ||
| − | '''func''' insert(x : '''Node''', z : ''' | + | '''func''' insert(x : '''Node''', z : '''T'''): <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый ключ</font> |
| − | '''if''' | + | '''if''' x == ''null'' |
| − | + | x = Node(z) <font color="green">// подвесим Node с key = z</font> | |
| − | + | '''else if''' z < x.key | |
| − | |||
insert(x.left, z) | insert(x.left, z) | ||
| − | '''else | + | '''else if''' z > x.key |
| − | |||
| − | |||
| − | |||
insert(x.right, z) | insert(x.right, z) | ||
| Строка 169: | Строка 151: | ||
'''if''' p.right == v | '''if''' p.right == v | ||
p.right = ''null'' | p.right = ''null'' | ||
| − | '''else | + | '''else if''' v.left == ''null'' '''or''' v.right == ''null'' <font color="green">// второй случай: удаляемый элемент имеет одного потомка</font> |
| − | + | '''if''' v.left == ''null'' | |
| − | + | '''if''' p.left == v | |
| − | + | p.left = v.right | |
| − | |||
| − | |||
| − | |||
| − | |||
'''else''' | '''else''' | ||
| − | + | p.right = v.right | |
| − | + | v.right.parent = p | |
| − | + | '''else''' | |
| − | + | '''if''' p.left == v | |
| − | + | p.left = v.left | |
| − | '''else''' | ||
| − | |||
| − | |||
| − | '''if''' | ||
| − | |||
| − | |||
| − | |||
'''else''' | '''else''' | ||
| − | successor.parent.right = successor.right | + | p.right = v.left |
| − | + | v.left.parent = p | |
| − | + | '''else''' <font color="green">// третий случай: удаляемый элемент имеет двух потомков</font> | |
| + | successor = next(v, t) | ||
| + | v.key = successor.key | ||
| + | '''if''' successor.parent.left == successor | ||
| + | successor.parent.left = successor.right | ||
| + | '''if''' successor.right != ''null'' | ||
| + | successor.right.parent = successor.parent | ||
| + | '''else''' | ||
| + | successor.parent.right = successor.right | ||
| + | '''if''' successor.right != ''null'' | ||
| + | successor.right.parent = successor.parent | ||
====Рекурсивная реализация==== | ====Рекурсивная реализация==== | ||
При рекурсивном удаления узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма {{---}} <tex>O(h)</tex>. | При рекурсивном удаления узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма {{---}} <tex>O(h)</tex>. | ||
Рекурсивная функция, возвращающая дерево с удаленным элементом <tex>z</tex>: | Рекурсивная функция, возвращающая дерево с удаленным элементом <tex>z</tex>: | ||
| − | '''Node''' delete(root : '''Node''', z : ''' | + | '''Node''' delete(root : '''Node''', z : '''T'''): <font color="green">// корень поддерева, удаляемый ключ</font> |
'''if''' root == ''null'' | '''if''' root == ''null'' | ||
'''return''' root | '''return''' root | ||
| − | '''if''' z | + | '''if''' z < root.key |
root.left = remove(root.left, z) | root.left = remove(root.left, z) | ||
| − | '''else if''' z | + | '''else if''' z > root.key |
root.right = remove(root.right, z) | root.right = remove(root.right, z) | ||
'''else if''' root.left != ''null'' '''and''' root.right != ''null'' | '''else if''' root.left != ''null'' '''and''' root.right != ''null'' | ||
Версия 19:17, 31 мая 2015
Бинарное дерево поиска обладает следующим свойством: если — узел бинарного дерева с ключом , то все узлы в левом поддереве должны иметь ключи, меньшие , а в правом поддереве большие .
Содержание
Операции в бинарном дереве поиска
Для представления бинарного дерева поиска в памяти будем использовать следующую структуру:
struct Node: T key // ключ узла Node left // указатель на левого потомка Node right // указатель на правого потомка Node parent // указатель на предка
Обход дерева поиска
Есть три операции обхода узлов дерева, отличающиеся порядком обхода узлов:
- — обход узлов в отсортированном порядке,
- — обход узлов в порядке: вершина, левое поддерево, правое поддерево,
- — обход узлов в порядке: левое поддерево, правое поддерево, вершина.
func inorderTraversal(x : Node):
if x != null
inorderTraversal(x.left)
print x.key
inorderTraversal(x.right)
При выполнении данного обхода вершины будут выведены в следующем порядке: 1 3 4 6 7 8 10 13 14.
func preorderTraversal(x : Node)
if x != null
print x.key
preorderTraversal(x.left)
preorderTraversal(x.right)
При выполнении данного обхода вершины будут выведены в следующем порядке: 8 3 1 6 4 7 10 14 13.
func postorderTraversal(x : Node)
if x != null
postorderTraversal(x.left)
postorderTraversal(x.right)
print x.key
При выполнении данного обхода вершины будут выведены в следующем порядке: 1 4 7 6 3 13 14 10 8.
Данные алгоритмы выполняют обход за время , поскольку процедура вызывается ровно два раза для каждого узла дерева.
Поиск элемента
Для поиска элемента в бинарном дереве поиска можно воспользоваться следующей процедурой, которая принимает в качестве параметров корень дерева и искомый ключ. Для каждого узла функция сравнивает значение его ключа с искомым ключом. Если ключи одинаковы, то функция возвращает текущий узел, в противном случае функция вызывается рекурсивно для левого или правого поддерева. Узлы, которые посещает функция образуют нисходящий путь от корня, так что время ее работы , где — высота дерева.
Node search(x : Node, k : T):
if x == null or k == x.key
return x
if k < x.key
return search(x.left, k)
else
return search(x.right, k)
Поиск минимума и максимума
Чтобы найти минимальный элемент в бинарном дереве поиска, необходимо просто следовать указателям от корня дерева, пока не встретится значение . Если у вершины есть левое поддерево, то по свойству бинарного дерева поиска в нем хранятся все элементы с меньшим ключом. Если его нет, значит эта вершина и есть минимальная. Аналогично ищется и максимальный элемент. Для этого нужно следовать правым указателям.
Node minimum(x : Node):
if x.left == null
return x
return minimum(x.left)
Node maximum(x : Node):
if x.right == null
return x
return maximum(x.right)
Данные функции принимают корень поддерева, и возвращают минимальный (максимальный) элемент в поддереве. Обе процедуры выполняются за время .
Поиск следующего и предыдущего элемента
Реализация с использованием информации о родителе
Если у узла есть правое поддерево, то следующий за ним элемент будет минимальным элементом в этом поддереве. Если у него нет правого поддерева, то нужно следовать вверх, пока не встретим узел, который является левым дочерним узлом своего родителя. Поиск предыдущего выполнятся аналогично. Если у узла есть левое поддерево, то следующий за ним элемент будет максимальным элементом в этом поддереве. Если у него нет левого поддерева, то нужно следовать вверх, пока не встретим узел, который является правым дочерним узлом своего родителя.
Node next(x : Node):
if x.right != null
return minimum(x.right)
y = x.parent
while y != null and x == y.right
x = y
y = y.parent
return y
Node prev(x : Node):
if x.left != null
return maximum(x.left)
y = x.parent
while y != null and x == y.left
x = y
y = y.parent
return y
Обе операции выполняются за время .
Реализация без использования информации о родителе
Рассмотрим поиск следующего элемента для некоторого ключа . Поиск будем начинать с корня дерева, храня текущий узел и узел - последний посещенный узел, ключ которого больше . Спускаемся вниз по дереву, как в алгоритме поиска узла, обновляя при переходе к левому потомку (если мы перешли к правому потомку, значит, что ключ в предыдущем узле меньше ). Таким образом, мы обойдем всё дерево и найдем элемент с минимальным ключем, большем . Аналогично реализуется операция поиска предыдущего элемента.
Node next(x : T):
Node current = root, parent = null // root — корень дерева
while current != null
if current.key > x
parent = current
current = current.left
else
current = current.right
return parent
Вставка
Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент.
Реализация с использованием информации о родителе
func insert(x : Node, z : T): // x — корень поддерева, z — вставляемый ключ
if z > x.key
if x.right != null
insert(x.right, z)
else
Node y
y.parent = x
y.key = z
x.right = y
else if z < x.key
if x.left != null
insert(x.left, z)
else
Node y
y.parent = x
y.key = z
x.left = y
Реализация без использования информации о родителе
func insert(x : Node, z : T): // x — корень поддерева, z — вставляемый ключ
if x == null
x = Node(z) // подвесим Node с key = z
else if z < x.key
insert(x.left, z)
else if z > x.key
insert(x.right, z)
Время работы алгоритма для обеих реализаций — .
Удаление
Нерекурсивная реализация
Для удаления узла из бинарного дерева поиска нужно рассмотреть три возможные ситуации. Если у узла нет дочерних узлов, то у его родителя нужно просто заменить указатель на . Если у узла есть только один дочерний узел, то нужно создать новую связь между родителем удаляемого узла и его дочерним узлом. Наконец, если у узла два дочерних узла, то нужно найти следующий за ним элемент (у этого элемента не будет левого потомка), его правого потомка подвесить на место найденного элемента, а удаляемый узел заменить найденным узлом. Таким образом, свойство бинарного дерева поиска не будет нарушено. Время работы алгоритма — .
| Случай | Иллюстрация |
|---|---|
| Удаление листа | |
| Удаление узла с одним дочерним узлом | |
| Удаление узла с двумя дочерними узлами | |
func delete(t : Node, v : Node): // дерево и удаляемый элемент
p = v.parent // предок удаляемого элемента
if v.left == null and v.right == null // первый случай: удаляемый элемент - лист
if p.left == v
p.left = null
if p.right == v
p.right = null
else if v.left == null or v.right == null // второй случай: удаляемый элемент имеет одного потомка
if v.left == null
if p.left == v
p.left = v.right
else
p.right = v.right
v.right.parent = p
else
if p.left == v
p.left = v.left
else
p.right = v.left
v.left.parent = p
else // третий случай: удаляемый элемент имеет двух потомков
successor = next(v, t)
v.key = successor.key
if successor.parent.left == successor
successor.parent.left = successor.right
if successor.right != null
successor.right.parent = successor.parent
else
successor.parent.right = successor.right
if successor.right != null
successor.right.parent = successor.parent
Рекурсивная реализация
При рекурсивном удаления узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма — . Рекурсивная функция, возвращающая дерево с удаленным элементом :
Node delete(root : Node, z : T): // корень поддерева, удаляемый ключ
if root == null
return root
if z < root.key
root.left = remove(root.left, z)
else if z > root.key
root.right = remove(root.right, z)
else if root.left != null and root.right != null
root.key = minimum(root.right).key
root.right = remove(root, root.right)
else
if root.left != null
root = t.left
else
root = t.right
return root
См. также
Источники информации
- Википедия — Двоичное дерево поиска
- Wikipedia — Binary search tree
- Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ = Introduction to Algorithms / Под ред. И. В. Красикова. — 2-е изд. — М.: Вильямс, 2005. — 1296 с. — ISBN 5-8459-0857-4

