280 likes | 432 Views
Алгоритмы на деревьях. Определение дерева. 8. class Tree { static private class TreeNode { Object key; TreeNode left, right; public TreeNode(Object key, TreeNode left, TreeNode right) { this .key = key;
E N D
Алгоритмы на деревьях. Определение дерева 8 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); } } 10 6 3 9 12 1 4
Простые рекурсивные алгоритмы 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); } }); } }
Внешний итератор 10 8 8 6 3 10 6 3 9 12 1 4 1 4 6 8 9 10 12 3
Внешний итератор 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); } }
6 3 12 1 4 Деревья поиска. Индексация и поиск данных. Поиск в дереве по ключу interface Comparable { int compareTo(Object obj); } 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
на; 12 берегу; 6 пустынных; 1 волн; 4 8 стоял; 9 он; 10 10 6 дум; 3 великих; 8 3 9 12 1 4 Индексация данных С помощью поиска по индексу можно получить ответы на вопросы: • Какое слово встречается ровно 6 раз? • Какие слова встречаются больше 10 раз? • Какое слово встречается чаще всего?
8 7 10 6 11 3 9 12 1 4 Добавление данных в лист дерева 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; } }
8 10 6 3 9 12 1 4 Добавление данных в корень дерева public 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); } } 7
1 10 0 13 0 0 12 15 Сбалансированные по высоте (АВЛ) деревья 1 8 2 0 1 2 1 0 7 10 6 0 1 0 0 1 0 0 0 0 4 9 3 9 12 -1 0 0 0 0 1 6 1 4 0 3 В общем случае трудно придумать алгоритм, позволяющий сбалансировать произвольное дерево, но можно предложить алгоритм балансировки дерева после добавления или удаления одного узла.
Алгоритм «простого поворота» 1. Отсоединение поддеревьев 2. Поворот 2 0 3. Присоединение поддеревьев 3 0 1 IV 2 (h) 1 III 1 (h) I II (h) (h-1) (I) < (1) < (II) < (2) < (III) < (3) < (IV)
Алгоритм «двойного поворота» 1. Отсоединение поддеревьев 2. Поворот 2 0 3. Присоединение поддеревьев 3 0 -1 -1 1 IV (h) I 1 (h) 2 II III (h) (h-1) (I) < (1) < (II) < (2) < (III) < (3) < (IV)
0 10 1 0 6 18 1 0 -1 0 3 8 15 20 0 0 0 0 12 17 23 1 Пример вставки ключа 0 10 1 -2 -1 6 15 0 1 0 -1 1 0 3 8 12 20 0 1 0 -1 0 1 18 23 0 17
Красно-черные деревья 17 Свойства красно-черных деревьев • Корень и пустые узлы всегда имеют черный цвет 14 21 • Красная вершина не может иметь красных потомков • Максимальные «черные» длины путей, ведущих из корня к листьям (пустым узлам), одинаковы 10 16 19 23 7 12 15 20 3 Красно-черные деревья поиска достаточно эффективны:число шагов при поиске не превышает 2 log2 n Существуют эффективные процедуры добавления и удаления узлов, которые не нарушают свойств красно-черных деревьев
Алгоритм добавления ключа в красно-черное дерево 17 • Добавляем ключ 19 • Добавляем ключ 20 10 26 • Добавляем ключ 11 • Добавляем ключ 3… 6 15 21 29 19 4 7 13 24 1 11 20 3
1712 147 21 4 Массив: 3 0 7 1 104 16 2 19 2 23 1 10 2 12 3 14 4 15 5 72 12 1 15 1 20 1 16 6 17 7 19 8 31 20 9 21 10 23 11 Хранение дополнительной информации в деревьях. Задача: по заданному порядковому номеру найти узел дерева; и наоборот, по заданному узлу найти его порядковый номер. Решение: в каждом узле хранить размер поддерева с корнем в этом узле
Алгоритмы работы с индексами в двоичных деревьях с размерами Поиск позиции по индексу: 1712 i = 5 TreeNode findByIndex(int i) • Начинаем цикл поиска с корняcurrent = root; 147 21 4 • Если в левом поддереве (== i) узлов,return current; • Если в левом поддереве (> i) узлов,current = current.left; 104 16 2 19 2 23 1 i = 0 • Если в левом поддереве (< i) узлов,i -= (current.left.size + 1);current = current.right; 72 12 1 15 1 20 1 Вход:i == 5 Выход:key == 15 31
Программа поиска позиции узла в дереве по индексу 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; } } }
Алгоритмы работы с индексами в двоичных деревьях с размерами Поиск индекса по позиции: 1712 TreeNode findByPosition(TreeNodenode) • Начинаем цикл поиска с узла node;ndx = node.left.size; 147 21 4 • Если узел справа от родительского,ndx += (brother.size+1); • Переходим к родительскому узлу,node = node.parent; 104 16 2 19 2 23 1 ndx = 1 ndx = 6 Вход:key == 16 Выход:ndx == 6 72 12 1 15 1 20 1 31
Программа поиска индекса узла в дереве по позиции 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; } class Tree { private TreeNode root = null; public static class TreeNode { Comparable info; int size; TreeNode parent, left, right; }
B sB A sA s1 s3 B sB A sA s1 s2 s2 s3 sA == s1 + sB + 1 sA == s1 + s2 + 1 sB == s2 + s3 + 1 sB == sA + s3 + 1 Алгоритм: int prev_sA = sA; sA = sB; sB += s2 – prev_sA; Преобразование информации о размерах при вращениях
2-3-дерево 18 32 7 13 24 37 41 1 4 9 12 16 21 26 29 34 39 44 46 Структура 2-3-дерева: • Каждый узел содержит 1 или 2 ключа • Ключи упорядочены (возможен быстрый поиск) • Промежуточные узлы имеют все ссылки (2 или 3 ссылки) • Все терминальные узлы (листья) находятся на одном уровне
Алгоритмы вставки ключа в 2-3-дерево 0 48 18 32 33 7 13 24 37 41 1 4 9 12 16 21 26 29 34 39 44 46 Новый ключ вставляется в лист одним из трех методов: • Расширением терминального узла • «Переливанием» • Расщеплением узлов
Алгоритмы удаления ключа из 2-3-дерева 18 32 7 13 24 37 41 1 4 9 12 16 21 26 29 34 39 44 46 Существующий ключ переносится в лист и удаляется одним из трех методов: • Сужением терминального узла • «Переливанием» • Склеиванием узлов
Обобщение 2-3-дерева – В-дерево k-го порядка Пример структуры при k = 3 Структура В-дерева: • Корневой узел содержит от 1 до 2*k ключей • Прочие узлы содержат от k до 2*k ключей • Ключи упорядочены (возможен быстрый поиск) • Промежуточные узлы имеют все ссылки (корень – от 2, остальные – от (k+1) до (2*k+1) ссылки) • Все терминальные узлы (листья) находятся на одном уровне
Расщепление узла В-дерева k-го порядка при вставке ключа (2 * k + 1)ключ k кл. 1 k кл. • При вставке ключа в терминальный узел образовалось переполнение узла • Делим узел на 3 узла: k, 1 и k ключей • Перемещаем средний ключ на предыдущий уровень
Модифицированное В-дерево (В+-дерево) • Только терминальные узлы содержат ссылки на данные; промежуточные узлы служат только для поиска информации. • При расщеплении узла происходит создание копии ключа: 1 2*k + 1 k + 1 k • При слиянии узлов ключ, пришедший с более высокого уровня, уничтожается: 1 k-1 2*k - 1 k