МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ «ВОЛГОГРАДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» КАМЫШИНСКИЙ ТЕХНОЛОГИЧЕСКИЙ ИНСТИТУТ (ФИЛИАЛ) ГОУ ВПО «ВОЛГОГРАДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» КАФЕДРА «АВТОМАТИЗИРОВАННЫЕ СИСТЕМЫ ОБРАБОТКИ ИНФОРМАЦИИ И УПРАВЛЕНИЯ» НЕЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: ДЕРЕВЬЯ Методические указания к лабораторной работе по дисциплине «Структуры и алгоритмы обработки данных» Волгоград 2011 УДК 004.42(07) Н 49 НЕЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: ДЕРЕВЬЯ: методические указания к лабораторной работе по дисциплине «Структуры и алгоритмы обработки данных» / Сост. А. Э. Панфилов; Волгоград. гос. техн. ун-т. – Волгоград, 2011. – 15 с. Содержат необходимые материалы для поддержки лабораторной работы по изучению и применению одной из разновидностей нелинейных структур данных – бинарного дерева. Предназначены для студентов, обучающихся по направлению 654600 «Информатика и вычислительная техника». Ил. 3. Библиогр.: 6 назв. Рецензент В. И. Кручинин Печатается по решению редакционно-издательского совета Волгоградского государственного технического университета 2 Волгоградский государственный технический университет, 2011 Лабораторная работа №3. НЕЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: ДЕРЕВЬЯ Цель работы: Освоить и закрепить приемы работы с данными различного типа, организованными в виде деревьев и их частного случая – бинарных деревьев. Получить навыки реализации структуры дерева. Ключевые понятия, которые необходимо знать: нелинейные структуры данных, деревья, лес, бинарные деревья, обход дерева, концевые вершины. Время выполнения лабораторного занятия: 4 часа. Количество баллов (минимальное, максимальное): 6 – 9 баллов. 1 ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1.1 Основные понятия и определения Дерево – иерархическая структура данных, хранящая набор элементов. Каждый элемент имеет значение и может указывать на ноль или более других элементов. На каждый элемент указывает только один другой элемент. Единственным исключением является корень дерева, на который не указывает ни один элемент. Входящие в дерево элементы называются его узлами. Бинарное дерево – это дерево, у которого каждый узел имеет не более двух дочерних узлов, причем левый и правый узлы различаются. Упорядоченное бинарное дерево (бинарное дерево сортировки) – бинарное дерево, обладающее следующим свойством: значения в узлах дерева располагаются таким образом, что для любого узла x значение всех узлов в его левом поддереве не превышают значение узла x, а значение всех узлов в его правом поддереве не меньше значения узла x. 1.2 Реализация структуры «дерево» Хотя структура данных «дерево» и относится к стандартным структурам данных, оно не имеет класса в библиотеках среды программирования Microsoft Visual Studio. Поэтому разработчикам приходится самостоятельно реализовывать поведение деревьев. Ниже приведен проект BinaryTree программной реализации упорядоченного бинарного дерева на языке С#. using System; namespace BinaryTree { //*** Класс - узел бинарного дерева *** public class TreeNode { 3 public int info; //информационная часть узла public TreeNode left; //левый потомок данного узла public TreeNode right; //правый потомок данного узла //Конструктор класса (value-информационная часть узла) public TreeNode(int value) { info = value; } } //*** Класс - бинарное дерево *** class BinaryTree { TreeNode root; //корень дерева //Добавить узел со значением value в дерево /* Функция вернет false если в дереве уже имеется узел с таким значением (новый узел не добавится),если добавление успешно - функция вернет true */ public bool AddNode(int value) { if (root == null) //если дерева еще нет, { // то добавить новый узел и сделать его корнем root = new TreeNode(value); return true; } TreeNode temp = root; //временная переменная для узла дерева (приравнивается корню) //цикл от корня дерева к листьям - ищем место для добавляемого узла do { //если текущий узел имеет такое же значение, if (temp.info == value) return false; // то добавление не успешно //если значение текущего узла меньше добавляемого, if (temp.info < value) {//то добавляемый узел должен быть справа от текущего //если правая связь пустая, то добавить новый узел if (temp.right == null) { temp.right = new TreeNode(value); return true; } //иначе перейти к правому узлу else temp = temp.right; } else //иначе, { //добавляемый узел должен быть слева от текущего узла if (temp.left == null) { temp.left = new TreeNode(value); return true; } else temp = temp.left; 4 } } while (temp != null);//цикл повторять, пока текущий узел (temp) не пустой //чтобы компилятор "не ругался" - вернуть что-нибудь (false) return false; } //Прямой обход дерева public void ShowInOrder() { //вызов рекурсивного метода обхода дерева (начиная с root) RecursiveInOrder(root); } //Вспомогательный метод - рекурсивный прямой обход дерева //node - узел с которого начинается обход private void RecursiveInOrder(TreeNode node) { if(node!=null)//если узел не пуст { //вывести корень, продолжить обход с левого узла, затем с правого Console.WriteLine(node.info); RecursiveInOrder(node.left); RecursiveInOrder(node.right); } } } } Демонстрация работы с методами класса BinaryTree //Главная функция консольного приложения static void Main(string[] args) { int value, code; BinaryTree tree = new BinaryTree(); //объект-бинарное дерево do { //вывод меню работы с бинарным деревом Console.WriteLine(" Работа с бинарным деревом:"); Console.WriteLine("1. Добавить элемент в дерево"); Console.WriteLine("2. Прямой обход дерева"); Console.WriteLine("0. Завершить работу"); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } //разбор вариантов действий switch (code) { case 1: Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); if(!tree.AddNode(value)) 5 Console.WriteLine("Добавление не произошло, т.к. элемент со значением {0} уже имеется в дереве",value); break; case 2: tree.ShowInOrder(); break; case 0: break; default: Console.WriteLine("Неверный выбор пункта меню"); break; } Console.WriteLine(); } while (code != 0); } 1.3 Примеры практического использования структуры «Дерево» 1.3.1 Алгоритм разбора формулы на базе бинарного дерева Существуем достаточно много алгоритмов разбора математического выражения (формулы) хранящегося в виде символьной строки. Одним из них является алгоритм разложения формулы на базе бинарного дерева, который разбирает исходное математическое выражение и строит по нему соответствующее бинарное дерево. Пример. Математическое выражение: « a + b / c * d – e / f ». Это же выражение в виде бинарного дерева представлено на рисунке 1. – + / a * e / b f d c Рисунок 1 – Математическая формула в виде бинарного дерева Узлом дерева формулы является либо знак операции, либо имя переменной, либо число. Для узла со знаком операции операндами являются левый и правый узлы-потомки (а точнее, поддеревья, корнями которых являются левый и правый узел-потомок) соответствующего узла. 6 Вычисление по формуле, представленной в виде бинарного дерева, производится рекурсивно по следующим правилам: 1. Если текущий узел – имя переменной, то ответ – значение этой переменной; 2. Если текущий узел – число, то ответ – значение числа; 3. Если текущий узел – операция, то она применяется к своим левому и правому узлам-потомкам. Далее на языке исполнителя приведена программа для представления математической формулы в виде бинарного дерева. В математической формуле допускаются применение: знаков операций – «+», «–», «*», «/» и чисел. СТРУКТУРА узел {узел бинарного дерева} . S : строка {строка с формулой для данного узла} . oper : цело {код содержимого узла} {-1 = пустой узел (пустая формула, 1 = сложение, 2 = вычитание} {3 = умножение, 4 = деление, 6 = число (значение в поле value)} . value : вещественное {значение числа (при oper = 6)} . left : указатель на узел {указатель на левый операнд} . right : указатель на узел {указатель на правый операнд} КОНЕЦ СТРУКТУРЫ ПРОГРАММА Декомпозиция формулы (elem: узел ) : целое . ДАНО:| узел, в поле S которого находится математическая формула . ПОЛУЧИТЬ:| двоичное дерево соответствующее формуле из поля S. Elem – корень этого дерева. Программа возвращает значение 0 – если дерево удачно построено, 1 – если имеется ошибка в формуле . ИСПОЛЬЗУЕМЫЕ ПОДПРОГРАММЫ . . длина строки (str : строка) : целое . . пустая строка (str : строка) . . выделить память (var : указатель) . . подстрока (str : строка; beg_index, end_index : целое) : строка . . перевод строки в число (str : строка) : вещественное . КОНЕЦ ИСПОЛЬЗУЕМЫХ ПОДПРОГРАММ . ИСПОЛЬЗУЕМЫЕ ПЕРЕМЕННЫЕ . . Len : целое {длина строки формулы} . . i : целое {индекс очередного символа строки формулы} . . S1 : строка {подстрока формулы} . . operation : булевый тип {наличие операций ‘*’ или ‘/’ в строке формулы} . . value : вещественное {значение числа встреченного в формуле} . КОНЕЦ ИСПОЛЬЗУЕМЫХ ПЕРЕМЕННЫХ . НАЧАЛО . Len = длина строки(elem.s) . ЕСЛИ Len = 0 ТО . . //УТВЕРЖДЕНИЕ: формула – пустая строка . . elem.oper = -1 . . завершить программу и вернуть значение 1 7 . ИНАЧЕ . . пустая строка(S1) . . ЦИКЛ ДЛЯ i [Len – 1, 0] ВЫПОЛНЯТЬ . . . ЕСЛИ elem.S[i] = ‘+’ ИЛИ elem.S[i] = ‘-’ ТО . . . . ЕСЛИ elem.S[i] = ‘+’ ТО . . . . . elem.oper = 1 . . . . ИНАЧЕ . . . . . elem.oper = 2 . . . . КОНЕЦ ЕСЛИ . . . . //УТВЕРЖДЕНИЕ: для данного узла создаем потомков . . . . выделить память(elem.left) . . . . выделить память(elem.right) . . . . elem.left.S = подстрока(elem.S, 0, i -1) . . . . elem.right.S = подстрока(elem.S, i +1, Len-1) . . . . пустая строка(S1) . . . . ЕСЛИ Декомпозиция формулы (elem.left) = 1 ТО . . . . . завершить программу и вернуть значение 1 . . . . КОНЕЦ ЕСЛИ . . . . ЕСЛИ Декомпозиция формулы (elem.right) = 1 ТО . . . . . завершить программу и вернуть значение 1 . . . . КОНЕЦ ЕСЛИ . . . . ПРЕРВАТЬ ЦИКЛ . . . ИНАЧЕ . . . . S1 = elem.S[i] + S1 . . . КОНЕЦ ЕСЛИ . . КОНЕЦ ЦИКЛА . . //УТВЕРЖДЕНИЕ: операций ‘+’ или ‘–‘ в формуле нет . . Len = длина строки(S1) . . ЕСЛИ Len = 0 ТО . . . завершить программу и вернуть значение 0 . . ИНАЧЕ . . . operation = false . . . ЦИКЛ ДЛЯ i [Len – 1, 0] ВЫПОЛНЯТЬ . . . . ЕСЛИ S1[i] = ‘*’ ИЛИ S1[i] = ‘/’ ТО . . . . . operation = true . . . . . //УТВЕРЖДЕНИЕ : в формуле есть операция ‘*’ или ‘/’ . . . . . ПРЕРВАТЬ ЦИКЛ . . . . КОНЕЦ ЕСЛИ . . . КОНЕЦ ЦИКЛА . . . ЕСЛИ operation = true ТО . . . . ЕСЛИ S1[i] = ‘*’ ТО . . . . . elem.oper = 3 . . . . ИНАЧЕ . . . . . elem.oper = 4 . . . . КОНЕЦ ЕСЛИ . . . . //УТВЕРЖДЕНИЕ: для данного узла создаем потомков 8 . . . . выделить память(elem.left) . . . . выделить память(elem.right) . . . . elem.left.S = подстрока(S1, 0, i -1) . . . . elem.right.S = подстрока(S1, i +1, Len-1) . . . . ЕСЛИ Декомпозиция формулы (elem.left) = 1 ТО . . . . . завершить программу и вернуть значение 1 . . . . КОНЕЦ ЕСЛИ . . . . ЕСЛИ Декомпозиция формулы (elem.right) = 1 ТО . . . . . завершить программу и вернуть значение 1 . . . . КОНЕЦ ЕСЛИ . . . ИНАЧЕ . . . . //УТВЕРЖДЕНИЕ: в формуле нет символа операции, формула – число . . . . elem.oper = 6 . . . . value = перевод строки в число (S1) . . . . ЕСЛИ value = 0 ТО . . . . . //УТВЕРЖДЕНИЕ: ошибка в переводе числа . . . . . завершить программу и вернуть значение 1 . . . . ИНАЧЕ . . . . . elem.value = value . . . . КОНЕЦ ЕСЛИ . . . КОНЕЦ ЕСЛИ . . КОНЕЦ ЕСЛИ . КОНЕЦ ЕСЛИ . завершить программу и вернуть значение 0 . конец ПРОГРАММЫ 1.3.2 Применение деревьев для определения стратегии игры Существует большое количество игр, в которых победа одного игрока является поражением другого (других) игрока. Такие игры относятся к играм с противоположными интересами. Примерами таких игр являются: шашки, шахматы, крестики-нолики и т.д. Для игр с противоположными интересами можно построить дерево стратегий игры, которое будет содержать все возможные ситуации в игре с их оценками (кто выиграет). Имея такое дерево стратегий можно целенаправленно выбирать такие ходы в игре, которые в конечном итоге приведут к победе игрока (или его минимальному проигрышу). Ниже приведен пример построения и использования дерева стратегий для игры «Палочки». Описание игры «Палочки» На столе лежит N штук палочек. В игре участвуют 2 игрока. Игроки поочередно делают свои ходы. Ход игрока заключается в том, что игрок берет со стола от 1 до 3 палочек. Игрок, который последним возьмет палочку со стола, считается проигравшим. 9 Представление дерева стратегий Состояние игры «Палочки» (игровая ситуация) характеризуется количеством палочек оставшихся на столе. После очередного хода игровая ситуация (количество палочек на столе) меняется. Ход игры можно представить как последовательное изменение игровых состояний. Хранить информацию обо всех последовательностях игровых состояний можно в виде структуры данных – дерева. Каждому игровому состоянию будет соответствовать узел дерева, а каждому возможному ходу игроков – ребро дерева. В качестве узлов дерева стратегий игры «Палочки» будем использовать узлы, хранящие следующую информацию (рисунок 2): - количество палочек на столе (COUNT); - кто гарантированно выиграет игру, начиная с текущего состояния (в предположении, что игроки играют наилучшим для себя образом) (WIN); - массив указателей, хранящих связи с другими узлами дерева, описывающие игровые состояния для каждого возможного хода игрока. Количество связей (размер массива) равен трем, по количеству возможных ходов игрока (взять одну палочку со стола, взять две палочки, взять три палочки) POINTERS[3]. COUNT WIN POINTERS[3] Рисунок 2 – Представление узла дерева стратегий игры Пример представления дерева стратегий с начальным количеством палочек равным 3, представлено на рисунке 3. Первое число в узле описывает количество палочек, оставшихся на столе, второе число – кто гарантированно выиграет игру, начиная с данного игрового состояния (если каждый игрок играет по наилучшей для себя стратегии). Для каждого узла левая связь (ребро) соответствует ходу игрока, при котором он берет 1 палочку; центральная связь – взятие 2-х палочек; правая связь – взятие 3-х палочек. 10 Õî ä 1 è ãðî êà (1 ÿðóñ) 3, 1 2, 2 1, 2 0, 1 1, 1 0, 2 0, 1 0, 2 Õî ä 2 è ãðî êà (2 ÿðóñ) Õî ä 1 è ãðî êà (3 ÿðóñ) Õî ä 2 è ãðî êà (4 ÿðóñ) Рисунок 3 - Дерево стратегий игры «Палочки» с начальным количеством - 3 палочки Таким образом, видно, что игру из 3-х палочек выиграет первый игрок, причем для этого он должен взять 2 палочки (пойти по центральной связи от корневого элемента). При других начальных ходах первый игрок проиграет (если второй игрок будет играть наилучшим для себя образом). Построение дерева стратегий Построение дерева стратегий начинается с корня. В корень дерева в поле COUNT записываем начальное количество палочек на столе N. Создаем три новых узла, которые в поле COUNT записываем числа равные N-1, N-2, N-3 соответственно. Связываем корневой узел с этими узлами, причем в поле POINTERS[0] (левая связь узла на рисунке 3) записываем адрес узла с COUNT равным N-1, в поле POINTERS[1] (центральная связь узла на рисунке 3) записываем адрес узла с COUNT равным N-2, в поле POINTERS[2] (правая связь узла на рисунке 3) записываем адрес узла с COUNT равным N-3. Далее для каждого из новых узлов создаем по три потомка и их адреса заносим в поля массива POINTERS, аналогично тому, как и для корневого узла. Так продолжается до тех пор, пока значение, записываемое в узел, остается не отрицательным. Далее необходимо заполнить поля WIN для каждого узла дерева. Заполнение полей WIN происходит от концевых узлов дерева к корню по следующим правилам: 1. Если поле COUNT равно 0, то полю WIN надо присвоить номер игрока, который делает ход на этом ярусе. 2. Если рассматриваемый узел находится на нечетном ярусе и его 11 поле COUNT не равно 0, то необходимо проверить у непосредственных потомков данного узла, содержат ли они в поле WIN значение равное 1 (хотя бы один из узлов-потомков). Если да, то в поле WIN рассматриваемого узла записать 1, иначе 2. 3. Если рассматриваемый узел находится на четном ярусе и его поле COUNT не равно 0, то необходимо проверить у непосредственных потомков данного узла, содержат ли они в поле WIN значение равное 2 (хотя бы один из узлов-потомков). Если да, то в поле WIN рассматриваемого узла записать 2, иначе 1. Использование дерева стратегий Состояние, соответствующее началу игры, находится в корневом узле дерева стратегий. В ходе игры необходимо отслеживать текущее игровое состояние, путем перемещения по дереву стратегий. Это делается просто – если текущий игрок берет 1 палочку, то надо перейти на левого потомка текущего узла дерева (адрес в POINTRS[0]); если берется 2 палочки, то переход осуществляется на потомка записанного в POINTRS[1]; если берется 3 палочки, то производится переход на потомка из POINTRS[2]. В случае, когда надо решить какой ход необходимо сделать компьютеру (считаем, что компьютер соответствует первому игроку), то для текущего узла дерева ищется узел-потомок, имеющий в поле WIN значение 1. В зависимости от того, на какой связи находится такой узел и делается ход (берется соответствующее количество палочек). Если для текущего узла нет узлов-потомков с WIN=1, то делается любой ход (например, взять 1 палочку). 2 ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ Задание 1 (5 баллов). Разобраться с примером использования бинарного дерева приведенным в проекте BinaryTree (пример реализации бинарного дерева). Задание 2 (+1 балла). Используя проект с примером реализации упорядоченного бинарного дерева (проект BinaryTree), дополнить класс BinaryTree следующими методами: - Проверка на наличие узла с заданным значением в дереве; - Обратный обход дерева; - Последовательный обход дерева. Изменить главную функцию Main(), включив в неё вызовы добавленных методов для проверки правильности их работы. Задание 3 (+ 2 балла). Дополнить класс упорядоченного бинарного дерева методом удаления узла с заданным значением. 12 Задание 4 (+ 2 балла). Реализовать алгоритм трансляции арифметической формулы на базе бинарного дерева (см. п. 1.3.1). Написать программу для демонстрации работы трансляции формулы с последующим ее вычислением. Задание 5 (+ 3 балла). Написать программу построения и использования дерева стратегий для игры «Палочки» (см. п.1.3.2). 3 КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что такое бинарное дерево? 2. Что такое упорядоченное бинарное дерево? 3. Способы обхода деревьев. Их особенности. 4. Как происходит добавление нового узла в упорядоченное бинарное дерево? 5. Как происходит удаление заданного узла из упорядоченного бинарного дерева? 6. Для каких задач может использоваться структура «дерево»? 7. Для какого класса игр можно применять дерево стратегий? Приведите примеры таких игр. Литература 1. Седжвик, Р. Фундаментальные алгоритмы на С++. Анализ. Структуры данных. Сортировка. Поиск: Пер. с англ. / Р. Седжвик. – К.: Издательство «ДиаСофт», 2001. – 688 с. 2. Красиков, И. В. Алгоритмы. Просто как дважды два / И. В. Красиков, И. Е. Красикова. – М.: Эксмо, 2006. – 256 с. 3. Вирт, Н. Алгоритмы и структуры данных / Н. Вирт. – СПб.:Невский Диалект, 2008.– 352 с. 4. Кнут, Д. Искусство программирования. В 3 т. Т. 1. Основные алгоритмы /Д. Кнут; 3-е изд.; пер. с англ. – М.: Издательский дом «Вильямс», 2000. – 687 с. 5. Структуры и организация данных [Электронный ресурс]: национальный технический университет Украины, кафедра информатики и интеллектуальной собственности. – 2000. – Режим доступа: http://khpi-iip.mipk.kharkiv.edu/library/datastr. 6. Портал «Информационно-коммуникационные технологии в образовании»: раздел «Структуры и алгоритмы обработки данных» [Электронный ресурс]. – 2010. –http:// www.ict.edu.ru/lib/index.php?a=elib&c=getForm&r=resNode&d=mod&id_node=220. 13 СОДЕРЖАНИЕ 1 ТЕОРЕТИЧЕСКАЯ ЧАСТЬ ..............................................................3 1.1 Основные понятия и определения ................................................3 1.2 Реализация структуры «дерево» ...................................................3 1.3 Примеры практического использования структуры «Дерево» ..6 2 ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ ............................... 12 3 КОНТРОЛЬНЫЕ ВОПРОСЫ .........................................................13 Литература........................................................................................... 13 14 Составитель: Александр Эдуардович Панфилов НЕЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: ДЕРЕВЬЯ Методические указания к лабораторной работе по дисциплине «Структуры и алгоритмы обработки данных» Под редакцией автора Темплан 2011 г., поз. № 22К. Подписано в печать 27. 12. 2011 г. Формат 60×84 1/16. Бумага листовая. Печать офсетная. Усл. печ. л. 0,93. Уч.-изд. л. 0,9. Тираж 50 экз. Заказ № Волгоградский государственный технический университет 400131, г. Волгоград, пр. Ленина, 28, корп. 1. Отпечатано в КТИ 403874, г. Камышин, ул. Ленина, 5, каб. 4.5 15