Деревья поиска.

advertisement
Алгоритмы на деревьях.
Определение дерева
8
10
6
3
1
9
4
12
class Tree {
static private class TreeNode {
Object key;
TreeNode left, right;
public TreeNode(Object key,
TreeNode left,
TreeNode right) {
this.key = key;
this.left = left;
this.right = right;
}
public TreeNode(Object key) {
this(key, null, null);
}
}
TreeNode root = null;
public Tree() {}
public Tree(Tree left, Object key, Tree right) {
root = new TreeNode(key, left.root, right.root);
}
}
Простые рекурсивные алгоритмы
class Tree {
static private class TreeNode {
Object key;
TreeNode left, right;
}
TreeNode root = null;
public int height() {
return height(root);
}
private int height(TreeNode root) {
if (root == null)
return 0;
else
return Math.max(height(root.left), height(root.right)) + 1;
}
}
public int level(int n) {
return level(root, n);
}
private int level(TreeNode root, int n) {
return root == null ? 0 :
n == 0 ? 1 :
level(root.left, n-1) + level(root.right, n-1);
}
Внутренний итератор
public interface Visitor {
void visit(Object node);
}
class Main {
public static void main(String[] args) {
Tree t = ...;
class Tree {
static private class TreeNode {
Object key;
TreeNode left, right;
}
t.iterate(new Visitor() {
public void visit(Object node) {
System.out.println(node);
}
});
}
TreeNode root = null;
public void iterate(Visitor v) {
iterate(root, v);
}
private void iterate(TreeNode root,
Visitor v) {
if (root != null) {
iterate(root.left, v);
v.visit(root.key);
iterate(root.right, v);
}
}
}
}
Внешний итератор
10
8
6
3
8
10
6
3
1
1
9
12
4
3
4
6
8
9
10 12
Внешний итератор
class Tree {
private static class TreeIterator implements Iterator {
Stack stk = new Stack();
TreeNode current;
public TreeIterator(TreeNode root) {
current = root;
if (current != null) findLeft();
}
private void findLeft() {
while (current.left != null) {
stk.push(current);
current = current.left;
}
}
public boolean hasNext() {
return current != null;
}
public Object next() {
if (current == null) throw new NoSuchElementException();
TreeNode res = current;
current = current.right;
if (current != null) findLeft(); else if (!stk.isEmpty()) {
current = (TreeNode)stk.pop();
}
return res.key;
}
public void remove() { throw new UnsupportedOperationException(); }
}
public Iterator iterator() { return new TreeIterator(root); }
}
Внешний итератор для обхода дерева «по уровням»
class Tree {
private static class TreeIterator1 implements Iterator {
Queue que = new Queue();
public TreeIterator1(TreeNode root) {
if (root != null) que.enqueue(root);
}
public boolean hasNext() {
return !que.isEmpty();
}
public Object next() {
if (que.isEmpty()) throw new NoSuchElementException();
TreeNode res = (TreeNode)que.dequeue();
if (res.left != null) que.enqueue(res.left);
if (res.right != null) que.enqueue(res.right);
return res.ref;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public Iterator iterator() { return new TreeIterator(root); }
}
Деревья поиска. Индексация и поиск данных.
interface Comparable {
int compareTo(Object obj);
}
Ищем ключ 9
8
Поиск в дереве по ключу
class Tree {
static private class TreeNode {
Comparable key;
Object ref;
TreeNode left, right;
}
TreeNode root = null;
3
1
public Object search(Comparable key) {
for (TreeNode current = root;
current != null; ) {
int res = key.compareTo(current.key);
if (res < 0) {
current = current.left;
} else if (res == 0) {
return current.ref;
} else { // res > 0
current = current.right;
}
}
return null;
}
10
6
9
12
4
}
Индексация данных
на;
8
берегу;
3
1
9
6
пустынных; 1
10
6
12
12
волн;
4
стоял;
9
он;
10
дум;
3
великих;
8
4
С помощью поиска по индексу можно получить ответы на вопросы:
 Какое слово встречается ровно 6 раз?
 Какие слова встречаются больше 10 раз?
 Какое слово встречается чаще всего?
Добавление данных в лист дерева
public void addLeaf(Comparable key, Object obj) {
TreeNode newNode = new TreeNode(key, obj);
TreeNode current = root;
TreeNode pred = null;
8
10
6
3
1
7
4
9
while (current != null) {
int res = key.compareTo(current.key);
if (res == 0) return;
else {
pred = current;
current =
(res < 0 ?
current.left :
current.right);
}
}
if (pred == null)
root = newNode;
else {
if (key.compareTo(pred.key) < 0)
pred.left = newNode;
else
pred.right = newNode;
}
12
11
}
Добавление данных в корень дерева
public void addRoot(Comparable key, Object obj) {
TreeNode newNode = new TreeNode(key, obj);
addRoot(key, root, newNode, 0, newNode, 1);
root = newNode;
}
7
8
10
6
3
1
9
4
12
private void addRoot(Comparable key,
TreeNode current,
TreeNode leftRef,
int lrLeft,
TreeNode rightRef,
int lrRight) {
if (current == null) {
if (lrLeft == 0)
leftRef.left = null;
else
leftRef.right = null;
if (lrRight == 0)
rightRef.left = null;
else
rightRef.right = null;
} else if (key.compareTo(current.key) > 0) {
if (lrLeft == 0)
leftRef.left = current;
else
leftRef.right = current;
addRoot(key, current.right,
current, 1, rightRef, lrRight);
} else {
if (lrRight == 0)
rightRef.left = current;
else
rightRef.right = current;
addRoot(key, current.left,
leftRef, lrLeft, current, 0);
}
}
Сбалансированные по высоте (АВЛ) деревья
1
1
10
8
1
2
1
0
0
3
1
0
9
0
4
0
7
10
6
0
0
2
1
0
13
00
0
1
0
4
12
9
-1
0
12
0
1
0
0
6
0
3
В общем случае трудно придумать алгоритм, позволяющий сбалансировать
произвольное дерево, но можно предложить алгоритм балансировки дерева
после добавления или удаления одного узла.
15
Алгоритм «простого поворота»
1. Отсоединение поддеревьев
2. Поворот
3. Присоединение поддеревьев
2
0
3
0
1
2
IV (h)
1
1
(h)
I
III (h)
II (h-1)
(I) < (1) < (II) < (2) < (III) < (3) < (IV)
Алгоритм «двойного поворота»
1. Отсоединение поддеревьев
2. Поворот
3. Присоединение поддеревьев
2
0
3
-1
0
-1
1
(h)
IV (h)
1
I
2
(h)
II
III (h-1)
(I) < (1) < (II) < (2) < (III) < (3) < (IV)
Пример вставки ключа
0
0
10
10
1
6
1
0
8
20
0
18
0
17
-1
0
23
18
0
3
1
0
1
1
-1
1
0
12
0
6
15
0
3
1
-2
-1
8
15
20
0
0
1
-1
0
0
12
17
0
23
Красно-черные деревья
17
Свойства красно-черных деревьев
14
 Корень и пустые узлы всегда имеют
черный цвет
21
 Красная вершина не может иметь
красных потомков
10
7
3
16
12
15
19
23
 Максимальные «черные» длины путей,
ведущих из корня к листьям (пустым
узлам), одинаковы
20
Красно-черные деревья поиска достаточно эффективны:
число шагов при поиске не превышает 2 log2 n
Существуют эффективные процедуры добавления и удаления
узлов, которые не нарушают свойств красно-черных деревьев
Алгоритм добавления ключа в красно-черное дерево
17
1. Добавляем ключ 19
2. Добавляем ключ 20
10
26
3. Добавляем ключ 11
4. Добавляем ключ 3…
6
4
1
15
7
13
11
3
21
19
29
24
20
Хранение дополнительной информации в деревьях.
Задача: по заданному порядковому номеру найти узел дерева;
и наоборот, по заданному узлу найти его порядковый номер.
Массив:
0
3
1
7
2
10
3
12
4
14
5
15
6
16
7
17
8
19
9
20
10
21
11
23
17
12
Решение: в каждом узле
хранить размер
поддерева с корнем в этом узле
14
7
21
4
10
4
7
2
3
1
16
2
12
1
15
1
19
2
23
1
20
1
Алгоритмы работы с индексами в двоичных деревьях с размерами
Поиск позиции по индексу:
17
12
TreeNode findByIndex(int i)
1.
Начинаем цикл поиска с корня
current = root;
2.
Если в левом поддереве (== i) узлов,
return current;
3.
Если в левом поддереве (> i) узлов,
current = current.left;
4.
14
7
i == 5
Выход:
key == 15
21
4
10
4
Если в левом поддереве (< i) узлов,
i -= (current.left.size + 1);
current = current.right;
Вход:
i = 5
7
2
3
1
16
2
12
1
15
1
i =190
23
1
2
20
1
Программа поиска позиции узла в дереве по индексу
class Tree {
private TreeNode root = null;
public static class TreeNode {
Comparable info;
int
size;
TreeNode
parent, left, right;
}
private TreeNode findByIndex(int i) {
// Pre: root != null &&
//
0 <= i < root.size
TreeNode current = root;
for (;;) {
// Inv: 0 <= i < current.size
int sizeL = 0;
if (current.left != null)
sizeL = current.left.size;
if (i == sizeL)
return current;
else if (i < sizeL)
current = current.left;
else {
i -= sizeL+1;
current = current.right;
}
}
}
Алгоритмы работы с индексами в двоичных деревьях с размерами
Поиск индекса по позиции:
17
12
TreeNode findByPosition(TreeNode node)
1.
Начинаем цикл поиска с узла node;
ndx = node.left.size;
2.
Если узел справа от родительского,
ndx += (brother.size+1);
3.
Переходим к родительскому узлу,
node = node.parent;
Вход:
key == 16
Выход:
ndx == 6
14
7
10
4
7
2
3
1
21
4
16
2
ndx = 6
1
12
1
15
1
19
2
23
1
20
1
Программа поиска индекса узла в дереве по позиции
class Tree {
private TreeNode root = null;
public static class TreeNode {
Comparable info;
int
size;
TreeNode
parent, left, right;
}
private int findByPosition(TreeNode node) {
int ndx = 0;
if (node.left != null)
ndx += node.left.size;
for (TreeNode p = node.parent;
p != null;
p = p.parent) {
if (p.right == node) {
int sizeL = 0;
if (p.left != null)
sizeL = p.left.size;
ndx += sizeL+1;
}
node = p;
}
return ndx;
}
Преобразование информации о размерах при вращениях
B
sB
A
sA
s1
A
sA
s3
s2
B
sB
s1
s2
sA == s1 + s2 + 1
sA == s1 + sB + 1
sB == sA + s3 + 1
sB == s2 + s3 + 1
Алгоритм:
int prev_sA = sA;
sA = sB;
sB += s2 – prev_sA;
s3
2-3-дерево
18 32
7 13
1
4
9 12
24
16
21
26 29
37 41
34
39
44 46
Структура 2-3-дерева:
 Каждый узел содержит 1 или 2 ключа
 Ключи упорядочены (возможен быстрый поиск)
 Промежуточные узлы имеют все ссылки (2 или 3 ссылки)
 Все терминальные узлы (листья) находятся на одном уровне
Алгоритмы вставки ключа в 2-3-дерево
0
18 32
7 13
1
4
9 12
48
33
24
16
21
26 29
37 41
34
39
44 46
Новый ключ вставляется в лист одним из трех методов:
 Расширением терминального узла
 «Переливанием»
 Расщеплением узлов
Алгоритмы удаления ключа из 2-3-дерева
18 32
7 13
1
4
9 12
24
16
21
26 29
37 41
34
39
44 46
Существующий ключ переносится в лист и удаляется одним из
трех методов:
 Сужением терминального узла
 «Переливанием»
 Склеиванием узлов
Обобщение 2-3-дерева – В-дерево k-го порядка
Пример структуры при k = 3
Структура В-дерева:
 Корневой узел содержит от 1 до 2*k ключей
 Прочие узлы содержат от k до 2*k ключей
 Ключи упорядочены (возможен быстрый поиск)
 Промежуточные узлы имеют все ссылки
(корень – от 2, остальные – от (k+1) до (2*k+1) ссылки)
 Все терминальные узлы (листья) находятся на одном уровне
Расщепление узла В-дерева k-го порядка при вставке ключа
k кл.
(2 * k +11) ключ
k кл.
1. При вставке ключа в терминальный узел образовалось переполнение узла
2. Делим узел на 3 узла: k, 1 и k ключей
3. Перемещаем средний ключ на предыдущий уровень
Модифицированное В-дерево (В+-дерево)
1. Только терминальные узлы содержат ссылки на данные;
промежуточные узлы служат только для поиска информации.
2. При расщеплении узла происходит создание копии ключа:
1
k + 2*k
1 +1 k
3. При слиянии узлов ключ, пришедший с более высокого уровня, уничтожается:
1
k-12*k - 1
k
Download