Скачать презентацию Динамические структуры данных язык Си Тема 6 Деревья Скачать презентацию Динамические структуры данных язык Си Тема 6 Деревья

Si_Derevya.ppt

  • Количество слайдов: 33

Динамические структуры данных (язык Си) Тема 6. Деревья © К. Ю. Поляков, 2008 Динамические структуры данных (язык Си) Тема 6. Деревья © К. Ю. Поляков, 2008

2 Деревья директор гл. инженер гл. бухгалтер инженер бухгалтер ? Что общего во всех 2 Деревья директор гл. инженер гл. бухгалтер инженер бухгалтер ? Что общего во всех примерах?

3 Деревья Дерево – это структура данных, состоящая из узлов и соединяющих их направленных 3 Деревья Дерево – это структура данных, состоящая из узлов и соединяющих их направленных ребер (дуг), причем в каждый узел (кроме корневого) ведет ровно одна дуга. 2 Корень – это начальный узел дерева. Лист – это узел, из которого не выходит ни одной дуги. 5 корень 1 6 1 4 3 1 3 2 10 9 1 2 8 7 Какие структуры – не деревья? 1 4 3 2 3 6 3 2 5 4

4 Деревья ! С помощью деревьев изображаются отношения подчиненности (иерархия, «старший – младший» , 4 Деревья ! С помощью деревьев изображаются отношения подчиненности (иерархия, «старший – младший» , «родитель – ребенок» ). Предок узла x – это узел, из которого существует путь по стрелкам в узел x. Потомок узла x – это узел, в который существует путь по стрелкам из узла x. Родитель узла x – это узел, из которого существует дуга непосредственно в узел x. 1 2 3 4 5 6 Сын узла x – это узел, в который существует дуга непосредственно из узла x. Брат узла x (sibling) – это узел, у которого тот же родитель, что и у узла x. Высота дерева – это наибольшее расстояние от корня до листа (количество дуг).

5 Дерево – рекурсивная структура данных Рекурсивное определение: 1 1. Пустая структура – это 5 Дерево – рекурсивная структура данных Рекурсивное определение: 1 1. Пустая структура – это дерево. 2 3 2. Дерево – это корень и несколько связанных с ним деревьев. 4 5 Двоичное (бинарное) дерево – это 6 дерево, в котором каждый узел имеет не более двух сыновей. 1. Пустая структура – это двоичное дерево. 2. Двоичное дерево – это корень и два связанных с ним двоичных дерева (левое и правое поддеревья).

6 Двоичные деревья Применение: 1) поиск данных в специально построенных деревьях (базы данных); 2) 6 Двоичные деревья Применение: 1) поиск данных в специально построенных деревьях (базы данных); 2) сортировка данных; 3) вычисление арифметических выражений; 4) кодирование (метод Хаффмана). Структура узла: struct Node { int data; // полезные данные Node *left, *right; // ссылки на левого // и правого сыновей }; typedef Node *PNode;

7 Двоичные деревья поиска Ключ – это характеристика узла, по которой выполняется поиск (чаще 7 Двоичные деревья поиска Ключ – это характеристика узла, по которой выполняется поиск (чаще всего – одно из полей структуры). ? Какая закономерность? 59 30 16 98 45 76 125 Слева от каждого узла находятся узлы с меньшими ключами, а справа – с бóльшими. Как искать ключ, равный x: 1) 2) 3) 4) если дерево пустое, ключ не найден; если ключ узла равен x, то стоп. если ключ узла меньше x, то искать x в левом поддереве; если ключ узла больше x, то искать x в правом поддереве. ? Сведение задачи к такой же задаче меньшей размерности – это …?

8 Двоичные деревья поиска Поиск в массиве (N элементов): 59 98 76 125 30 8 Двоичные деревья поиска Поиск в массиве (N элементов): 59 98 76 125 30 45 16 При каждом сравнении отбрасывается 1 элемент. Число сравнений – N. Поиск по дереву (N элементов): 59 30 16 98 45 76 125 При каждом сравнении отбрасывается половина оставшихся элементов. Число сравнений ~ log 2 N. быстрый поиск 1) нужно заранее построить дерево; 2) желательно, чтобы дерево было минимальной высоты.

9 Реализация алгоритма поиска //-------------------// Функция Search – поиск по дереву // Вход: Tree 9 Реализация алгоритма поиска //-------------------// Функция Search – поиск по дереву // Вход: Tree - адрес корня, // x - что ищем // Выход: адрес узла или NULL (не нашли) //-------------------PNode Search (PNode Tree, int x) дерево пустое: { ключ не нашли… if ( ! Tree ) return NULL; if ( x == Tree->data ) return Tree; нашли, возвращаем адрес корня if ( x < Tree->data ) return Search(Tree->left, x); else return Search(Tree->right, x); } искать в левом поддереве искать в правом поддереве

10 Как построить дерево поиска? //----------------------// Функция Add. To. Tree – добавить элемент к 10 Как построить дерево поиска? //----------------------// Функция Add. To. Tree – добавить элемент к дереву // Вход: Tree - адрес корня, // x - что добавляем //-----------------------void Add. To. Tree (PNode &Tree, int x) { адрес корня может if ( ! Tree ) { измениться Tree = new Node; Tree->data = x; Tree->left = NULL; дерево пустое: создаем Tree->right = NULL; новый узел (корень) return; } if ( x < Tree->data ) добавляем к левому или Add. To. Tree ( Tree->left, x ); правому поддереву else Add. To. Tree ( Tree->right, x ); } ! Минимальная высота не гарантируется!

11 Обход дерева – это перечисление всех узлов в определенном порядке. 59 30 Обход 11 Обход дерева – это перечисление всех узлов в определенном порядке. 59 30 Обход ЛКП ( «левый – корень – правый» ): 16 30 45 59 76 98 16 125 Обход ПКЛ ( «правый – корень – левый» ): 125 98 76 59 45 30 16 Обход КЛП ( «корень – левый – правый» ): 59 30 16 45 98 76 125 Обход ЛПК ( «левый – правый – корень» ): 16 45 30 76 125 98 59 98 45 76 125

12 Обход дерева – реализация //----------------------// Функция LKP – обход дерева в порядке ЛКП 12 Обход дерева – реализация //----------------------// Функция LKP – обход дерева в порядке ЛКП // (левый – корень – правый) // Вход: Tree - адрес корня //-----------------------void LKP( PNode Tree ) обход этой ветки закончен { if ( ! Tree ) return; обход левого поддерева LKP ( Tree->left ); вывод данных корня printf ( "%d ", Tree->data ); LKP ( Tree->right ); обход правого поддерева } ! Для рекурсивной структуры удобно применять рекурсивную обработку!

13 Разбор арифметических выражений Как вычислять автоматически: / (a + b) / (c + 13 Разбор арифметических выражений Как вычислять автоматически: / (a + b) / (c + d – 1) Инфиксная запись, обход ЛКП (знак операции между операндами) + a b a + b / c + d – 1 + c 1 d необходимы скобки! Префиксная запись, КЛП (знак операции до операндов) польская нотация, / + a b - + c d 1 Jan Łukasiewicz (1920) скобки не нужны, можно однозначно вычислить! Постфиксная запись, ЛПК (знак операции после операндов) a b + c d + 1 - / обратная польская нотация, F. L. Bauer and E. W. Dijkstra

14 Вычисление выражений Постфиксная форма: X = a b + c d + d 14 Вычисление выражений Постфиксная форма: X = a b + c d + d b a a 1 - 1 c a+b / c c+d c+d-1 a+b a+b a+b X Алгоритм: 1) взять очередной элемент; 2) если это не знак операции, добавить его в стек; 3) если это знак операции, то • взять из стека два операнда; • выполнить операцию и записать результат в стек; 4) перейти к шагу 1.

15 Вычисление выражений Задача: в символьной строке записано правильное арифметическое выражение, которое может содержать 15 Вычисление выражений Задача: в символьной строке записано правильное арифметическое выражение, которое может содержать только однозначные числа и знаки операций +-*. Вычислить это выражение. Алгоритм: 1) ввести строку; 2) построить дерево; 3) вычислить выражение по дереву. Ограничения: 1) 2) 3) 4) ошибки не обрабатываем; многозначные числа не разрешены; дробные числа не разрешены; скобки не разрешены.

16 Построение дерева k first k-1 last k+1 5 + 7 * 6 - 16 Построение дерева k first k-1 last k+1 5 + 7 * 6 - 3 * 2 Алгоритм: 1) если first=last (остался один символ – число), то создать новый узел и записать в него этот элемент; иначе. . . 2) среди элементов от first до last включительно найти последнюю операцию (элемент с номером k); 3) создать новый узел (корень) и записать в него знак операции; 4) рекурсивно применить этот алгоритм два раза: • построить левое поддерево, разобрав выражение из элементов массива с номерами от first до k-1; • построить правое поддерево, разобрав выражение из элементов массива с номерами от k+1 до last.

17 Как найти последнюю операцию? 5 + 7 * 6 - 3 * 2 17 Как найти последнюю операцию? 5 + 7 * 6 - 3 * 2 Порядок выполнения операций • умножение и деление; • сложение и вычитание. Приоритет (старшинство) – число, определяющее последовательность выполнения операций: раньше выполняются операции с большим приоритетом: • умножение и деление (приоритет 2); • сложение и вычитание (приоритет 1). ! Нужно искать последнюю операцию с наименьшим приоритетом!

18 Приоритет операции //----------------------// Функция Priority – приоритет операции // Вход: символ операции // 18 Приоритет операции //----------------------// Функция Priority – приоритет операции // Вход: символ операции // Выход: приоритет или 100, если не операция //----------------------int Priority ( char c ) сложение и { вычитание: switch ( c ) { приоритет 1 case '+': case '-': return 1; умножение и case '*': case '/': деление: return 2; приоритет 2 } return 100; это вообще не } операция

19 Номер последней операции //----------------------// Функция Last. Operation – номер последней операции // Вход: 19 Номер последней операции //----------------------// Функция Last. Operation – номер последней операции // Вход: строка, номера первого и последнего // символов рассматриваемой части // Выход: номер символа - последней операции //----------------------int Last. Operation ( char Expr[], int first, int last ) { int Min. Prt, i, k, prt; проверяем все Min. Prt = 100; символы for( i = first; i <= last; i++ ) { prt = Priority ( Expr[i] ); if ( prt <= Min. Prt ) { нашли операцию с Min. Prt = prt; минимальным k = i; приоритетом } } вернуть номер return k; символа }

20 Построение дерева Структура узла struct Node { char data; Node *left, *right; }; 20 Построение дерева Структура узла struct Node { char data; Node *left, *right; }; typedef Node *PNode; Создание узла для числа (без потомков) PNode Number. Node ( char c ) { PNode Tree = new Node; один символ, число Tree->data = c; Tree->left = NULL; Tree->right = NULL; return Tree; возвращает адрес } созданного узла

21 Построение дерева //----------------------// Функция Make. Tree – построение дерева // Вход: строка, номера 21 Построение дерева //----------------------// Функция Make. Tree – построение дерева // Вход: строка, номера первого и последнего // символов рассматриваемой части // Выход: адрес построенного дерева //----------------------PNode Make. Tree ( char Expr[], int first, int last ) { PNode Tree; осталось int k; только число if ( first == last ) return Number. Node ( Expr[first] ); k = Last. Operation ( Expr, first, last ); новый узел: Tree = new Node; операция Tree->data = Expr[k]; Tree->left = Make. Tree ( Expr, first, k-1 ); Tree->right = Make. Tree ( Expr, k+1, last ); return Tree; }

22 Вычисление выражения по дереву //----------------------// Функция Calc. Tree – вычисление по дереву // 22 Вычисление выражения по дереву //----------------------// Функция Calc. Tree – вычисление по дереву // Вход: адрес дерева // Выход: значение выражения //----------------------int Calc. Tree (PNode Tree) вернуть число, { если это лист int num 1, num 2; if ( ! Tree->left ) return Tree->data - '0'; num 1 = Calc. Tree( Tree->left); вычисляем num 2 = Calc. Tree(Tree->right); операнды switch ( Tree->data ) { (поддеревья) case '+': return num 1+num 2; case '-': return num 1 -num 2; выполняем case '*': return num 1*num 2; операцию case '/': return num 1/num 2; } некорректная return 32767; операция }

23 Основная программа //----------------------// Основная программа: ввод и вычисление // выражения с помощью дерева 23 Основная программа //----------------------// Основная программа: ввод и вычисление // выражения с помощью дерева //----------------------void main() { char s[80]; PNode Tree; printf ( "Введите выражение > " ); gets(s); Tree = Make. Tree ( s, 0, strlen(s)-1 ); printf ( "= %d n", Calc. Tree ( Tree ) ); getch(); }

24 Дерево игры Задача. Перед двумя игроками лежат две кучки камней, в первой из 24 Дерево игры Задача. Перед двумя игроками лежат две кучки камней, в первой из которых 3, а во второй – 2 камня. У каждого игрока неограниченно много камней. Игроки ходят по очереди. Ход состоит в том, что игрок или увеличивает в 3 раза число камней в какой-то куче, или добавляет 1 камень в какую-то кучу. Выигрывает игрок, после хода которого общее число камней в двух кучах становится не менее 16. Кто выигрывает при безошибочной игре – игрок, делающий первый ход, или игрок, делающий второй ход? Как должен ходить выигрывающий игрок?

25 Дерево игры игрок 1 игрок 2 9, 2 27, 2 3, 6 игрок 25 Дерево игры игрок 1 игрок 2 9, 2 27, 2 3, 6 игрок 1 3, 18 12, 2 4, 2 36, 2 4, 6 выиграл игрок 1 4, 18 5, 2 игрок 2 15, 2 12, 2 36, 2 4, 6 12, 6 5, 3 15, 3 4, 4 12, 4 9, 3 ключевой ход 27, 3 4, 3 3, 3 игрок 2 ! При правильной игре выиграет игрок 2!

26 Задания « 4» : «Собрать» программу для вычисления правильного арифметического выражения, включающего только 26 Задания « 4» : «Собрать» программу для вычисления правильного арифметического выражения, включающего только однозначные числа и знаки операций +, -, * , /. « 5» : То же самое, но допускаются также многозначные числа и скобки. « 6» : То же самое, что и на « 5» , но с обработкой ошибок (должно выводиться сообщение).

27 Конец фильма 27 Конец фильма

Красно-чёрное дерево — двоичное дерево поиска, в котором каждый узел имеет атрибут цвет, принимающий Красно-чёрное дерево — двоичное дерево поиска, в котором каждый узел имеет атрибут цвет, принимающий значения красный или черный. В дополнение к обычным требованиям, налагаемым на двоичные деревья поиска, к красно-чёрным деревьям применяются следующие требования: 1. Узел либо красный, либо чёрный. 2. Корень — чёрный. 3. Все листья(NIL) — черные. 4. Оба потомка каждого красного узла — черные. 5. Всякий простой путь от данного узла до любого листового узла, являющегося его потомком, содержит одинаковое число черных узлов. 28

29 Вставка узла начинается с добавления узла, точно так же, как и в обычном 29 Вставка узла начинается с добавления узла, точно так же, как и в обычном бинарном дереве поиска, и окрашивания его в красный цвет. Т. к. в к-ч дереве листья не содержат данных, поэтому добавляем красный узел с двумя черными потомками на место чёрного листа. typedef enum col {RED, BLACK} struct node { int value; node * left, * right; color; }; У нового узла N могут быть родитель P, предок G дядя U struct node * grandparent(struct node *n) { if ((n != NULL) && (n->parent != NULL)) return n->parent; else return NULL; } struct node * uncle(struct node *n) { struct node *g = grandparent(n); if (g == NULL) return NULL; // No grandparent means no uncle if (n->parent == g->left) return g->right; else return g->left; }

Случай 1: Текущий узел N в корне дерева. В этом случае, он 30 перекрашивается Случай 1: Текущий узел N в корне дерева. В этом случае, он 30 перекрашивается в чёрный цвет. Случай 2: Предок P текущего узла чёрный. В этом случае дерево действительно. Случай 3: Если и родитель P и дядя U — красные, то они оба могут быть перекрашены в чёрный и дедушка G станет красным. Теперь у текущего красного узла N чёрный родитель. Однако, дедушка G теперь может нарушить свойства 2 (Корень — чёрный) или 4 (Оба потомка каждого красного узла — черные) (свойство 4 может быть нарушено, так как родитель G может быть красным). Чтобы это исправить, вся процедура рекурсивно выполняется на G из случая 1.

Случай 4: Родитель P является красным, но дядя U — чёрный. Также, текущий узел Случай 4: Родитель P является красным, но дядя U — чёрный. Также, текущий узел N — правый потомок P, а P в свою очередь — левый потомок своего предка G. В этом случае может быть произведен поворот дерева, который меняет роли текущего узла N и его предка P. Случай 5: Родитель P является красным, но дядя U — чёрный, текущий узел N — левый потомок P и P — левый потомок G. В этом случае выполняется поворот дерева на G. В результате получается дерево, в котором бывший родитель P теперь является родителем и текущего узла N и бывшего дедушки G. 31

32 B-деревом называется дерево, удовлетворяющее следующим свойствам: 1. Каждый узел содержит хотя бы один 32 B-деревом называется дерево, удовлетворяющее следующим свойствам: 1. Каждый узел содержит хотя бы один ключ. Ключи в каждом узле упорядочены. Корень содержит от 1 до 2 t-1 ключей. Любой другой узел содержит от t-1 до 2 t-1 ключей. Листья не являются исключением из этого правила. Здесь t- параметр дерева, не меньший 2 (и обычно принимающий значения от 50 до 2000). 2. Любой узел кроме листа, содержащий N ключи содержит N+1 потомков. При этом 1. Первый потомок и все его потомки содержат ключи из интервала 2. Для , i-й потомок и все его потомки содержат ключи из интервала 3. Глубина всех листьев одинакова. Свойство 2 можно сформулировать иначе: каждый узел B-дерева, кроме листьев, можно рассматривать как упорядоченный список, в котором чередуются ключи и указатели на потомков.

33 Красно-чёрное дерево и В-дерево (сильноветвящееся дерево, 2 -3 -4 дерево) 33 Красно-чёрное дерево и В-дерево (сильноветвящееся дерево, 2 -3 -4 дерево)