Post on 01-Jan-2016
description
transcript
Алгоритмы на деревьях.
8
10
9
6
3
1 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 Tree { static private class TreeNode { Object key; TreeNode left, right; }
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); } }}
class Main { public static void main(String[] args) { Tree t = ...;
t.iterate(new Visitor() { public void visit(Object node) { System.out.println(node); } }); }}
Внешний итератор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);}
8
10
9
6
3
1 4
12
class Tree { static private class TreeNode { Comparable key; Object ref; TreeNode left, right; }
TreeNode root = null;
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; } }
Поиск в дереве по ключу
Ищем ключ 9
8
10
9
6
3
1 4
12
на; 12
берегу; 6
пустынных; 1
волн; 4
стоял; 9
он; 10
дум; 3
великих; 8
Индексация данных
С помощью поиска по индексу можно получить ответы на вопросы:
Какое слово встречается ровно 6 раз? Какие слова встречаются больше 10 раз?
Какое слово встречается чаще всего?
Добавление данных в лист дерева
8
10
9
6
3
1 4
12
11
7
public void addLeaf(Comparable key, Object obj) { TreeNode newNode = new TreeNode(key, obj); TreeNode current = root; TreeNode pred = null;
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; }}
Добавление данных в корень дерева
6
3
1 4
8
10
9 12
7public void addRoot(Comparable key, Object obj) { TreeNode newNode = new TreeNode(key, obj); addRoot(key, root, newNode, 0, newNode, 1); root = newNode;}
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); }}
Сбалансированные по высоте (АВЛ) деревья
8
10
9
6
3
1 4
12
0 0
0 0 0
0
1
21
01
В общем случае трудно придумать алгоритм, позволяющий сбалансировать произвольное дерево, но можно предложить алгоритм балансировки дерева после добавления или удаления одного узла.
7
94
1 6
3
0 0
0
0
1
0
10
13
12 15
1
0
00
-1
1
20
0
Алгоритм «простого поворота»
3
2
11
1
2
IV
III
III
(I) < (1) < (II) < (2) < (III) < (3) < (IV)
1. Отсоединение поддеревьев
(h) (h-1)
(h)
(h)
2. Поворот0
0
3. Присоединение поддеревьев
Алгоритм «двойного поворота»
3
1
21
-1
2
IV
I
IIIII
(I) < (1) < (II) < (2) < (III) < (3) < (IV)
1. Отсоединение поддеревьев
(h) (h-1)
(h)
(h)
2. Поворот3. Присоединение поддеревьев
0
0
-1
Пример вставки ключа
10
6 15
3 8 12 20
1 18 23
1
1 0
0
0
0
00
170
1
01
-1-2
-1
-1
10
6
153 8
121
1
1 0
0
0
17
18
20
230 0
0
0
0
-1
Красно-черные деревья
17
14 21
19 2310
7 12
Свойства красно-черных деревьев
Корень и пустые узлы всегда имеют черный цвет Красная вершина не может иметь красных потомков
Максимальные «черные» длины путей, ведущих из корня к листьям (пустым узлам), одинаковы
Красно-черные деревья поиска достаточно эффективны:число шагов при поиске не превышает 2 log2 n
Существуют эффективные процедуры добавления и удаления узлов, которые не нарушают свойств красно-черных деревьев
16
15 20
3
Алгоритм добавления ключа в красно-черное дерево
17
10 26
21 296
4 7
15
13 24
1
1. Добавляем ключ 19
19
2. Добавляем ключ 20
20
3. Добавляем ключ 11
11
4. Добавляем ключ 3…
3
Хранение дополнительной информации в деревьях.
Задача: по заданному порядковому номеру найти узел дерева;и наоборот, по заданному узлу найти его порядковый номер.
1712
147
214
192
231
104
72
121
162
151
201
31
Решение: в каждом узле хранить размер поддерева с корнем в этом узле
3
7
10
12
14
15
16
17
19
20
21
23
Массив:
0
1
2
3
4
5
6
7
8
9
10
11
Алгоритмы работы с индексами в двоичных деревьях с размерами
Поиск позиции по индексу:
TreeNode findByIndex(int i)
1. Начинаем цикл поиска с корняcurrent = root;
2. Если в левом поддереве (== i) узлов,return current;
3. Если в левом поддереве (> i) узлов,current = current.left;
4. Если в левом поддереве (< i) узлов,i -= (current.left.size + 1);current = current.right;
1712
147
214
192
231
104
72
121
162
151
201
31
Вход: i == 5
Выход: key == 15
i = 5
i = 0
Программа поиска позиции узла в дереве по индексу
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; } } }
Алгоритмы работы с индексами в двоичных деревьях с размерами
Поиск индекса по позиции:
TreeNode findByPosition(TreeNode node)
1. Начинаем цикл поиска с узла node;ndx = node.left.size;
2. Если узел справа от родительского,ndx += (brother.size+1);
3. Переходим к родительскому узлу,node = node.parent;
1712
147
214
192
231
104
72
121
162
151
201
31
Вход: key == 16
Выход: ndx == 6
ndx = 1ndx = 6
Программа поиска индекса узла в дереве по позиции
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; }
Преобразование информации о размерах при вращениях
BsB
AsA
s1 s2
s3
sA == s1 + s2 + 1
sB == sA + s3 + 1
Алгоритм: int prev_sA = sA;sA = sB;sB += s2 – prev_sA;
BsB
s3s2
AsA
s1
sA == s1 + sB + 1
sB == s2 + s3 + 1
2-3-дерево
18 32
7 13 37 4124
21 26 29 34 39 44 46169 121 4
Структура 2-3-дерева:
Каждый узел содержит 1 или 2 ключа Ключи упорядочены (возможен быстрый поиск) Промежуточные узлы имеют все ссылки (2 или 3 ссылки) Все терминальные узлы (листья) находятся на одном уровне
Алгоритмы вставки ключа в 2-3-дерево
18 32
7 13 37 4124
21 26 29 34 39 46169 121 4
Новый ключ вставляется в лист одним из трех методов:
Расширением терминального узла «Переливанием» Расщеплением узлов
3348
44
0
Алгоритмы удаления ключа из 2-3-дерева
18 32
7 13 37 4124
21 26 29 34 39 46169 121 4
Существующий ключ переносится в лист и удаляется одним из трех методов:
Сужением терминального узла «Переливанием» Склеиванием узлов
44
Обобщение 2-3-дерева – В-дерево k-го порядка
Структура В-дерева:
Корневой узел содержит от 1 до 2*k ключей
Ключи упорядочены (возможен быстрый поиск) Промежуточные узлы имеют все ссылки (корень – от 2, остальные – от (k+1) до (2*k+1) ссылки)
Все терминальные узлы (листья) находятся на одном уровне
Прочие узлы содержат от k до 2*k ключей
Пример структуры при k = 3
Расщепление узла В-дерева k-го порядка при вставке ключа
(2 * k + 1) ключ
1. При вставке ключа в терминальный узел образовалось переполнение узла
k кл. k кл.1
2. Делим узел на 3 узла: k, 1 и k ключей
3. Перемещаем средний ключ на предыдущий уровень
Модифицированное В-дерево (В+-дерево)
1. Только терминальные узлы содержат ссылки на данные; промежуточные узлы служат только для поиска информации.
2. При расщеплении узла происходит создание копии ключа:
2*k + 1k + 1 k
1
3. При слиянии узлов ключ, пришедший с более высокого уровня, уничтожается:
1
k-1 k2*k - 1