Министерство образования и науки российской Федерации ФГБОУ ВПО «Нижегородский государственный педагогический университет имени Козьмы Минина» Иорданский М.А. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ Учебно-методическое пособие Нижний Новгород 2013 УДК 681.3 ББК 32.81 Д 466 Д 466 Динамические структуры данных: учебно-методическое пособие / М.А.Иорданский. – Н.Новгород: НГПУ им. К.Минина, 2013. – 47 с. В пособии рассматривается динамическое использование памяти в среде Паскаль. Основными структурами данных являются односвязные и двусвязные списки. Приведены примеры решения задач и задания для самостоятельной работы. Пособие предназначено студентам факультета МИФ, изучающим программирование, дискретную математику, теоретические основы информатики, практикум по решению задач на ЭВМ. Материал из пособия может также быть полезным учителям информатики. УДК 681.3 ББК 32.81 НГПУ им. К.Минина, 2013 - 2 - ОГЛАВЛЕНИЕ Введение ……………………………………………………………. 4 Глава I. Классификация данных…………………………………....5 §1. Виды структур данных ……………………………………....5 §2. Динамическое использование памяти ……………………...7 Глава II. Односвязные списки……………………………………...9 §1. Линейные односвязные списки ЛОС ………………………9 §2. ЛОС с доступом к головному элементу (стек) …………...19 §3. ЛОС с доступом к крайним элементам (очередь) …….….21 §4. Циклическая очередь ……………………………………....24 Глава III. Двусвязные списки……………………………………..27 §1. Линейные двусвязные списки ……….……………….…...27 §2. Бинарные деревья….………………….…………………....30 Глава IV. Графы и динамические структуры данных…………..33 §1. Способы задания графов……………………………….…. 33 §2. Переход от одного способа задания к другому ………….39 Литература………………………………………………………...47 - 3 - Введение Выбор необходимой структуры данных для решения той или иной задачи может существенно влиять как на временную, так и на емкостную сложность решающего алгоритма. Актуальность такого выбора возрастает по мере усложнения решаемых задач. В предлагаемом учебно-методическом пособии приводится краткая сравнительная характеристика различных структур данных. Основное внимание уделяется динамическому использованию памяти. Подробно описывается работа с такими динамическими структурами данных, как односвязные и двусвязные списки. В качестве иллюстрационного материала выбраны, в основном, задачи представления графов и перехода от одного способа задания к другому. Приведены примеры решения задач и задания для самостоятельной работы студентов. - 4 - Глава I. Классификация данных §1. Виды структур данных Скалярные величины Они используются для задания отдельных независимых объектов. Каждая скалярная величина характеризуется своим именем и типом возможных значений. Так, например, квадратное уравнение ax2 + bx + c = 0 задается тремя скалярными величинами a, b и c типа real. Простота описания является, пожалуй, единственным достоинством скалярных данных. Среди недостатков наиболее существенным является невозможность организации эффективной обработки групп скалярных данных из-за отсутствия, в общем случае, регулярной взаимосвязи между отдельными элементами данных и их расположением в памяти. Например, для нахождения суммы числовых данных с именами a1, a2, …, a10 после исходной команды sum:=0; потребуется выполнения 10 команд присваивания: sum:=sum+a1; , . . . , sum:=sum+a10;. Массивы данных Между элементами массивов и их расположением в памяти устанавливается определенная взаимосвязь в процессе трансляции программы. Обычно, все элементы массива располагаются в памяти подряд, что облегчает организацию циклических процедур. Вычисление рассмотренной выше суммы может быть организовано, например, так sum:=0; for i:=1 to 10 do sum:=sum+a[i]; К недостаткам массивов данных относятся следующие ограничения: число элементов массива задается заранее при трансляции: элементы массивов должны быть одного типа; операции вставки или удаления элементов требуют перемещения и других элементов массива. - 5 - Файлы данных При их использовании не требуется заранее оговаривать число компонент файла. Поскольку файлы располагаются во внешней памяти, то они энергонезависимы и практически «безразмерны», учитывая значительный объем дисковой памяти. Второе ограничение массивов данных преодолевается в текстовых файлах, компонентами которых могут быть не только отдельные символы, но и числа. Однако, за это «удовольствие» приходится расплачиваться большей трудоемкостью реализации процедур вставки или удаления компонент текстового файла, поскольку в текстовых файлах невозможен прямой доступ к его компонентам. Что было возможно в массивах данных. Добиться настоящего разнообразия типов компонент можно путем использования в качестве таковых данные типа запись с разнотипными полями. Третье ограничение массивов данных сохраняется для любых файлов, как текстовых, так и типизированных. Списковые структуры данных Расположение элементов в таких структурах никак не связано с местом их размещения в памяти. Это достигается за счет добавления к каждому элементу специальных ссылочных полей, содержащих адреса смежных элементов структуры. Списки данных позволяют избавиться от третьего ограничения, свойственного как массивам, так и файлам. Для вставки или удаления элементов в таких структурах данных достаточно изменить значения ссылочных полей соответствующих элементов. В статических системах удаленные из списковых структур элементы не удаляются из памяти, что ведет к её неэкономному использованию. Динамические списковые структуры данных В таких списковых структурах при удалении элементов освобождается и соответствующая им память. Кроме того, память под новые элементы структуры выделяется по мере необходимости в процессе исполнения программы. Та- 6 - кой режим работы с памятью называется динамическим. Данные, структура которых может изменяться в ходе выполнения программы, образуют динамические структуры данных (ДСД). В зависимости от числа ссылочных полей в элементах списков различают односвязные, двусвязные и многосвязные списки. В зависимости от структуры взаимосвязей между элементами списков различают линейные и нелинейные списки. §2. Динамическое использование памяти Статические и динамические переменные Переменные, значения которых хранятся во внутренней памяти, могут быть двух видов: статические и динамические. Под статические переменные память выделяется изначально в ходе трансляции программы и сохраняется до конца работы программы. Под динамические переменные память выделяется и уничтожается по мере необходимости в ходе выполнения программы. Использование динамических переменных позволяет экономить память, создавать структуры данных, число элементов в которых заранее не известно; сохранять значения локальных динамических переменных по выходе из соответствующих процедур, что удобно при отладке программ. Обращение к динамическим переменным В отличие от статических переменных, за динамическими переменными заранее не закрепляется какой-либо участок памяти и поэтому они не имеют имен в обычном понимании. В качестве их выступают имена статических переменных специального ссылочного типа, называемых указателями, содержащих адреса областей памяти, в которых размещаются значения динамических переменных. Описание ссылочного типа осуществляется с помощью инструкции <имя ссылочного типа> = ^ <идентификатор типа>; Так, описание переменных p и q ссылочного типа s, указывающих на динамические переменные типа integer, выглядит следующим образом: type s=^integer; var p,q:s; - 7 - Переменные ссылочного типа могут находиться в 3 различных состояниях: быть не инициализированными (сразу после их описания); содержать начальный адрес памяти, в которой размещается соответствующая динамическая переменная; содержать специальное значение nil, когда указатель инициализирован, но не содержит адрес какого-либо участка памяти. В системе Турбо-Паскаль над переменными ссылочного типа можно выполнять операции сравнения, присваивания конкретных значений и передачи значения, как параметр: p>q; p:=250; p:=q; . При обращении к динамическим переменным к имени соответствующего указателя добавляется символ ^. Например, команда p^:=123; присваивает целочисленной динамической переменной, на которую указывает p, значение равное 123. Команда q^:= p^; присвоит значение 123 динамической переменной, на которую указывает q. Выделить память под новую динамическую переменную можно с помощью процедуры new (< имя указателя >); Для освобождения памяти от динамической переменной достаточно выполнить процедуру dispose (< имя указателя >); . Распределение динамической памяти Динамическая память занимает всю или часть свободной памяти, оставшейся после загрузки программы. Структура динамической памяти: Системная область Уд Список «дырок» Свободная память ут «Куча» У0 Программа - 8 - Динамическая память Поскольку в динамической памяти могут размещаться переменные различных типов, то её заполненную область называют ещё «кучей». Заполнение динамической памяти происходит при выполнении процедур new, а освобождение при выполнении процедур dispose. Вначале указатели начала динамической памяти у0 и начала свободной динамической памяти ут совпадают и заполнение происходит, начиная от у0. При освобождении памяти от динамических переменных в «куче» образуются «дырки». Информация о них (размер и положение) размещается в списке «дырок» кучи. Граница между списком дырок и свободной памятью фиксируется указателем уд. Выделение памяти при ут > у0 производится следующим образом: если список «дырок» не пуст, то вначале делается попытка заполнить одну из них. Программа – монитор «кучи» осуществляет заполнение, если при этом размер «дырки» сократиться не менее чем на половину. Информация в списке об этой «дырке» а также, быть может, и положение указателя уд соответствующим образом корректируются. Если подходящей «дырки» в списке не найдется, то переменная размещается в свободной памяти с соответствующим изменением положения указателя ут. Глава II. Односвязные списки §1. Линейные односвязные списки (ЛОС) Элементы ЛОС задаются динамическими переменными типа запись, содержащими одно ссылочное поле для связи с другими элементами ЛОС. Структуру ЛОС можно представить следующим образом: Начало (голова) списка 1 . Конец списка 2 . . . . n nil Создание ЛОС В зависимости от решаемой задачи, ЛОС удобно создавать как от конца к началу, так и от начала к концу. Если число элементов в ЛОС заранее не из- - 9 - вестно, то при создании ЛОС используются рекурсивные процедуры с параметром. Пример 1. Создать от конца к началу ЛОС из n элементов, каждый из которых содержит случайное целое число из диапазона от 0 до 9. Элементы списка задаются динамической переменной el типа запись (record), с двумя полями: информационным (inf), содержащим случайное целое число, и ссылочным (next), указывающим местоположение следующего элемента списка. В качестве указателей на динамическую переменную используется статические переменные p, q и r типа s. type s=^el; el=record inf:integer; next:s; end; var p,q,r:s; Вначале необходимо создать пустой список (p:=nil;). При добавлении в список нового элемента сначала необходимо выделить соответствующую память (new(q);), а затем сформировать значения полей (q^.inf:=random(10) q^.next:=p;). Первый раз в ссылочное поле будет занесено специальное значение nil, означающее конец списка, а после выполнения команды p:=q; при добавлении следующих элементов в ссылочное поле будет заноситься значение, указывающее местоположение в динамической памяти следующего элемента списка. Повторяющийся фрагмент программы выглядит следующим образом: new(q); q^.inf:=random(10); q^.next:=p; p:=q; Ниже приведена вся программа создания ЛОС от конца к началу. program sozdlosk; type s=^el; el=record inf:integer; next:s; end; var p,q:s; i,n:integer; begin randomize; - 10 - write(’n=’); readln(n); p:=nil; for i:=n downto 1 do begin new(q); q^.inf:=random(10); write(q^.inf,’ ’); q^.next:=p; p:=q; end; end. Замечания: 1. После окончания работы программы sozdlosk рабочие указатели p и q будут расположены на первом элементе ЛОС. В дальнейшем, для того чтобы не «потерять» созданный ЛОС, на его головном элементе всегда должен находиться некоторый указатель. 2. Длина создаваемых списков, обычно, заранее не известна (такая ситуация типична для ДСД) и поэтому удобно пользоваться рекурсивными алгоритмами, оформленными в виде процедур. Поскольку в одну программу обычно включается несколько процедур, использующих динамические переменные, то их указатели описываются один раз как глобальные переменные. Пример 2. Создать ЛОС от конца к началу с помощью рекурсивной процедуры rsozdlosk с параметром. Перед обращением к процедуре необходимо выполнить команду p:=nil;. procedure rsozdlosk (x:integer); begin if x>0 then begin new(q); q^.inf:=random(10); q^.next:=p; p:=q; rsozdlosk (x-1); end; end; Пример 3. Создать ЛОС от начала к концу с помощью рекурсивной процедуры rsozdlosn с параметром. Перед обращением к процедуре необходимо создать вспомогательный (нулевой) элемент ЛОС, выполнив команды new(p); и q:=p;. - 11 - procedure rsozdlosn (n:integer); begin if n>0 then begin new(r); r^.inf:=random(10); r^.next:=nil; q^.next:=r; q:=r; rsozdlosn (n-1); end; end; Пример 4. Вывести на экран значения информационных полей элементов ЛОС от начала к концу с помощью рекурсивной процедуры: procedure rprlosn; begin if q<>nil then begin write(q^.inf,' '); q:=q^.next; rprlosn; end; end; Перед обращением к процедуре необходимо установить указатель q на первый элемент ЛОС с помощью команды q:=p^.next;. Число элементов ЛОС может меняться в ходе выполнения программы путем добавления или удаления отдельных элементов. При этом расположение в памяти других элементов ЛОС остается неизменным, что является важным достоинством ДСД. Вставка элементов в ЛОС Для добавления элемента в ЛОС необходимо выполнить следующую последовательность действий: 1. Поставить рабочий указатель на элемент, после которого вставляется новый элемент. Для того, чтобы можно было вставить элемент и на первое ме- - 12 - сто, ЛОС формируют с нулевым вспомогательным элементом с помощью команды new(p);. Для того, чтобы поставить указатель q на предыдущий элемент к вставляемому элементу, необходимо выполнить команду q:=p;, а затем один из следующих циклов: for j:=1 to i do {для вставки после i-го элемента ЛОС} q:=q^.next; или while <отрицание свойства> do {для вставки после} q:=q^.next; { элемента ЛОС с заданным свойством} 2. Выделить память под вставляемый элемент и заполнить её информацией. Пусть указателем на динамическую переменную, задающую вставляемый элемент, является r. Тогда информация о вставляемом элементе задается командами new(r); r^.inf:=<значение информационного поля>; Для рассматриваемого ЛОС последняя команда будет иметь следующий вид r^.inf:=random(10); 3. Установить связи вставляемого элемента с другими элементами ЛОС. Вначале в ссылочное поле вставляемого элемента помещается значение ссылочного поля предыдущего элемента r^.next:=q^.next; а затем в ссылочное поле предыдущего элемента помещается значение указателя на вставляемый элемент q^.next:=r; Пример 5. Процедуры вставки элемента в ЛОС 1) после i-го элемента procedure vst (i:integer); var j:integer; begin q:=p; for j:=1 to i do q:=q^.next; new(r); - 13 - r^.inf:=<информация о вставляемом элементе > r^.next:=q^.next; q^.next:=r; end; 2) после элемента с заданным свойством procedure vstsv; var j:integer; begin q:=p; while <отрицание свойства> do q:=q^.next; new(r); r^.inf:=<информация о вставляемом элементе >; r^.next:=q^.next; q^.next:=r; end; Удаление элементов из ЛОС Для удаления элемента из ЛОС необходимо выполнить следующую последовательность действий: 1. Поставить один рабочий указатель (q) на предыдущий элемент, как и при вставке элементов в ЛОС. 2. Поставить другой рабочий указатель (r) на удаляемый элемент r:=q^.next; 3. Установить ссылку с предыдущего элемента ЛОС на последующий q^.next:=r^.next; 4. Освободить динамическую память от удаленного элемента dispose(r); Пример 6. Процедуры удаления элемента из ЛОС: 1) после i - го элемента procedure udal (i:integer); var j:integer; begin q:=p; for j:=1 to i do q:=q^.next; r:=q^.next; q^.next:=r^.next; dispose(r); end; - 14 - 2) после элемента с заданным свойством procedure udalsv; var j:integer; begin q:=p; while <отрицание свойства> do q:=q^.next; r:=q^.next; q^.next:=r^.next; dispose(r); end; Решение задач на обработку ЛОС Задача 1. Подсчитать среднее значение информационных полей элементов ЛОС. procedure crlos; var sum,n:integer; sa:real; begin q:=p^.next; sum:=0; n:=1; while q<>nil do begin sum:=sum+q^.inf; inc(n); q:=q^.next; end; sa:=sum/n; writeln('sa= ',sa:5:2); end; Задача 2. Подсчитать количество и порядковые номера элементов ЛОС, информационные поля которых удовлетворяют заданному условию. Воспользуемся глобальной переменной num типа array [1..n] of integer; procedure anlos; var i,j,k:integer; begin q:=p; k:=0; i:=1; j:=0; while q^.next<>nil do begin if <заданное условие> then begin inc(k); inc(j); num[j]:=i; end; q:=q^.next; inc(i); - 15 - end; writeln('всего ',k,' элементов'); writeln('порядковые номера элементов'); for i:=1 to j do write( num[i],' '); end; Задача 3. Упорядочить информационные поля элементов ЛОС в порядке неубывания. procedure upnulos; var c:integer; begin q:=p^.next; while q^.next<>nil do begin r:=q; repeat r:=r^.next; if r^.inf<q^.inf then begin c:=r^.inf; r^.inf:=q^.inf; q^.inf:=c; end; until r^.next=nil; q:=q^next; end; end; Задача 4. Провести анализ, упорядочены ли информационные поля элементов ЛОС, задаваемые случайными целыми числами, и если упорядочены, то в каком порядке. Для решения задачи можно ввести переменные v и u типа boolean. Изначально им присваивается значение false. При наличии ситуации, когда предыдущий элемент меньше следующего, переменной v присваивается значение true. При наличии ситуации, когда предыдущий элемент больше следующего, переменной u присваивается значение true. Анализ возможных комбинации значений переменных v и u позволит получить один из четырех следующих ответов: список не упорядочен, список упорядочен по неубыванию, список упорядочен по невозрастанию или список состоит из равных элементов. - 16 - program provlos; uses crt; type s=^el; el=record inf:integer; next:s; end; var i,n:integer; v,u:boolean; p,q,r:s; procedure rsozdlosn (n:integer); begin if n>0 then begin new(r); r^.inf:=random(10); r^.next:=nil; q^.next:=r; q:=r; rsozdlosn (n-1); end; end; procedure rprlosn; begin if q<>nil then begin write(q^.inf,' '); q:=q^.next; rprlosn; end; end; begin clrscr; randomize; new(p); q:=p; write('n='); readln(n); rsozdlosn(n); q:=p^.next; rprlosn; writeln; v:=false; u:=v; q:=p^.next; while (q^.next<>nil) and (not(v and u)) do begin if q^.inf < q^.next^.inf - 17 - then v:=true; if q^.inf > q^.next^.inf then u:=true; q:=q^.next; end; if v and u then writeln('список не упорядочен'); else if v then writeln('список упорядочен по неубыванию'); else if u then writeln('список упорядочен по невозрастанию'); else writeln('список состоит из равных элементов'); readln; end. Задания для самостоятельного выполнения 1. Создать ЛОС от начала к концу из n элементов, информационные поля которых являются случайными целыми числами из некоторого заданного диапазона. 2. Подсчитать количество и порядковые номера элементов ЛОС, значения информационных полей которых: 1) положительны; 2) отрицательны; 3) четны; 4) нечетны; 5) меньше заданного числа; 6) больше заданного числа; 7) равны значению информационного поля k-го (1 k n) элемента; 8) меньше значения информационного поля k-го (1 k n) элемента; 9) больше значения информационного поля k-го (1 k n) элемента; - 18 - 10) равны значению информационного поля предыдущего элемента; 11) равны значению информационного поля следующего элемента; 12) меньше значения информационного поля предыдущего элемента; 13) больше значения информационного поля предыдущего элемента; 14) меньше значения информационного поля следующего элемента; 15) больше значения информационного поля следующего элемента; 16) больше среднего значения информационных полей элементов; 17) меньше среднего значения информационных полей элементов. 18) равны наибольшему значению информационных полей элементов; 19) равны наименьшему значению информационных полей элементов; 3. Удалить из ЛОС элементы, информационные поля которых удовлетворяют заданному условию из п.2. 4. Упорядочить оставшиеся элементы ЛОС в заданном порядке. §2. ЛОС с доступом к головному элементу (стек) При исполнении рекурсивных алгоритмов обрабатываемые данные вначале последовательно заносятся в память, а затем извлекаются из неё в обратном порядке. Поэтому хранить их удобно в виде ЛОС с возможностью вставки или удаления лишь головного элемента (вершины стека). Пусть stek указатель вершины стека. stek n n-1 . . . 1 nil Добавление элементов в стек Рассмотрим создание стека целых чисел. Для создания пустого стека достаточно выполнить команду stek:=nil;. Для занесения элементов в стек можно воспользоваться процедурой inst с параметром n и рабочим указателем r: procedure inst (n:integer); begin new(r); r^inf:=n; r^next:=stek; stek:=r; end; - 19 - Процедура занесения элементов в стек может не иметь входного параметра. В таком случае перед командой r^inf:=n; необходимо вставить две команды write('n='); readln(n); и вводить значения элементов, добавляемых в стек. Удаление элементов из стека Процедура удаления элемента из стека имеет выходной параметр n для сохранения соответствующей информации. Для освобождения памяти от удаляемого элемента используется оператор dispose. procedure outst (var n:integer); begin r:=stek; n:=r^.inf; stek:=stek^.next; dispose (r); end; Решение задач с использованием стека Задача 1. Вычислить y = n!, используя рекурсивную функцию fakt(n). program faktrec; var n:integer; fanction fakt (n:integer):real; begin if n=1 then fakt:=1 else fakt:=n*fakt(n-1); end; begin write(’n=’); readln(n); writeln(’y=’, fakt (n) ); end. Задача 2. Вычислить y = n! без использования рекурсивной функции, моделируя работу стека. program faktmodr; type s=^el; el=record inf:integer; next:s; end; var i,j,n:integer; y:real; stek,r:s; - 20 - procedure inst (i:integer); begin new(r); r^inf:=i; r^next:=stek; stek:=r; end; procedure outst (var i:integer); begin r:=stek; i:=r^.inf; stek:=stek^.next; dispose (r); begin writeln(’n=’); readln(n); stek:=nil; for i:=n downto 1 do inst(i); y:=1; for i:=1 to n do begin outst(j); y:=y*j; end; writeln(’y=’, y); end. §3. ЛОС с доступом к крайним элементам (очередь) При работе с очередью необходимо использование двух указателей. Один из них (och) указывает на головной элемент, а другой (p) на последний элемент. och 1 ... 2 n nil p Добавление элементов в очередь Элементы добавляются в конец ЛОС. Вначале необходимо создать пустую очередь с помощью команд och:=nil; и p:=nil;. Далее для постановки элементов в очередь можно воспользоваться процедурой inoch с параметром n и рабочим указателем r: - 21 - procedure inoch (n:integer); begin new(r); r^inf:=n; r^next:=nil; if p=nil then och:=r else p^next:=r; p:=r; end; Удаление элементов из очереди Для удаления элементов из очереди можно использовать процедуру outoch с выходным параметром n и рабочим указателем r: procedure outoch (var n:integer); begin r:=och; n:=r^.inf; if och^.next=nil then begin och:=nil; p:=nil; end else och:=och^.next; dispose (r); end; Решение задачи с использованием очереди Задача. Для входной последовательности из n целых чисел вычислить суммы для всех групп из k (k n) расположенных подряд чисел. Для решения данной задачи удобно задать первые k чисел в виде очереди, вычислить сумму этих чисел, а затем изменять её, удаляя и добавляя по одному крайнему элементу. При таком способе вычислений происходит многократное использование промежуточных результатов (частичных сумм), что приводит к сокращению общего объема вычислений. Для облегчения проверки - 22 - работы программы в качестве исходных данных выберем последовательность случайные целые числа из диапазона от 0 до 9. program sumoch; type s=^el; el=record inf:integer; next:s; end; var p,q,r,och:s; i,n,k,s:integer; procedure inochs; begin new(r); r^.inf:=random(10); s:=s+r^.inf; write(’+’,r^.inf,’ ’ ); r^.next:=nil; if p=nil then och:=r else p^.next:=r; p:=r; end; procedure sozdoch (x:integer); begin och:=nil; p:=nil; for i:=1 to x do begin inochs; s:=s+r^.inf; end; writeln(’s=’,s); end. procedure outochs (var n:integer); begin r:=och;n:=r^.inf; if och^.next=nil then begin och:=nil; p:=nil; end else och:=och^.next; s:=s-r^.inf; write(’-’,r^.inf,’ ’ ); dispose (r); end; begin - 23 - randomize; write(’n =’); readln(n); write(’k (k n) =’); readln(k); s:=0; sozdoch (k); for i:=1 to n-k do begin outochs; inochs writeln(s); end; end. Выводимая в ходе работы программы информация структурирована следующим образом: + <1-ое число > + . . . + <k-ое число > s = < сумма первых k чисел> <удаляемое число > + <добавляемое число > s = < текущая сумма > < удаляемое число > + <добавляемое число > s = < текущая сумма > Задания для самостоятельного выполнения 1. Создать типизированный файл, содержащий n целых случайных чисел из диапазона от 0 до 9. 2. Вычислить суммы чисел для всех групп из k (k n) расположенных подряд компонент файла. 3. Найти наибольшее и наименьшее значения среди вычисленных сумм. §4. Циклическая очередь При решении задач сортировки по признаку, принимающему два возможных значения (бинарная сортировка) удобно использовать ЛОС, в котором ссылочное поле последнего элемента указывает на начальный элемент ЛОС. 1 2 ... n coch Использование такой «обратной связи» позволяет добавлять элементы в конец или удалять элементы из начала очереди с помощью одного указателя - 24 - coch, расположенного на её последнем элементе. Очередь с обратной связью называется циклической. Добавление элементов в циклическую очередь Элементы добавляются в конец ЛОС. Вначале необходимо создать пустую циклическую очередь с помощью команды сoch:=nil;. Далее для постановки элементов в циклическую очередь можно воспользоваться процедурой inсoch с параметром n и рабочим указателем r: procedure inсoch (n:integer); begin new(r); r^inf:=n; if coch=nil then r^.next:=r else begin r^.next:=coch^.next; coch^.next:=r; end; coch:=r; end; Удаление элементов из циклической очереди Для удаления элементов можно использовать процедуру outcoch с выходным параметром n и рабочим указателем r: procedure outcoch (var n:integer); begin r:=och^.next; n:=r^.inf; if coch=r then coch:=nil else coch^.next:=r^.next; dispose (r); end; - 25 - Решение задачи с использованием циклической очереди Задача. Упорядочить входную последовательность из n целых положительных и отрицательных случайных чисел таким образом, чтобы вначале располагались отрицательные числа. Для решения задачи достаточно при добавлении в очередь отрицательного числа указатель coch оставлять без изменения. program binsort; type s=^el; el=record inf:integer; next:s; end; var coch,q,r,:s; i,n,x:integer; procedure inсochm (x:integer); begin new(r); write(x,’ ’); r^inf:=x; if coch=nil then begin r^.next:=r; coch:=r; end else begin r^.next:=coch^.next; coch^.next:=r; if r^.inf >=0 then coch:=r; end; end; procedure prcoch; begin q:=coch; if q=nil then writeln (’coch=nil ’) else repeat q:=q^.next; write (q^.inf, ’ ’); until q=coch; end; begin randomize; write (’n=’); readln (n); coch:=nil; - 26 - for i:=1 to n do begin x:=random(19) - 9; inсochm (x); end; writeln; prcoch; end. Задания для самостоятельного выполнения 1. Создать типизированный файл, содержащий целые случайные числа из диапазона от -9 до 9. 2. Записать компоненты файла в циклическую очередь таким образом, чтобы в начале располагались положительные числа. 3. Сохранить информацию из циклической очереди во вновь созданном типизированном файле. 4. Вывести на экран и сравнить компоненты исходного и результирующего файлов. Глава III. Двусвязные списки §1. Линейные двусвязные списки Линейные односвязные списки обладают существенным недостатком. Они позволяют эффективно перемещаться от начала к концу ЛОС, используя адреса, указанные в ссылочных полях элементов. Для того чтобы обратиться к элементу с меньшим номером, потребуется поиск от начала ЛОС. Этот недостаток преодолевается в линейных двусвязных списках (ЛДС), элементы которых содержит по два ссылочных поля. В каждом элементе указываются соответственно адреса предыдущего и последующего элементов списка. 0 nil 1 2 ... n nil Для того, чтобы у каждого элемента ЛДС имелся предыдущий элемент, в голову списка обычно добавляют нулевой элемент. В этом элементе может храниться различная вспомогательная информация о ЛДС. - 27 - Описание типа элементов ЛДС: type s2=^el2; el2=record inf:integer; nextl,nextr:s2; end; var p2,q2,r2:s2; Ссылочные поля nextl и nextr содержат соответственно адреса предыдущего и последующего элементов ЛДС. Создание ЛДС Вначале можно создать от начала к концу линейный список, используя ссылочное поле nextr, с помощью процедуры rsozdlds, отличающейся от процедуры rsozdlosn заменой next на nextr, q на q2, r на r2. Затем заполнить ссылочные поля nextl с помощью процедуры: procedure lsozdlds (n:integer); begin if n>0 then begin q2^.nextr^.nextl:=q2; q2:=q2^.nextr; lrsozdlds (n-1); end; end; Весь ЛДС будет создан после выполнения процедуры: procedure sozdlds (n:integer); begin new(p2);r2:=p2; rsozdlds (n); q2:=p2; lsozdlds(n); p2^.nextl:=nil; end; Вставка элементов в ЛДС Поскольку из каждого элемента ЛДС имеются ссылки, как на последующий, так и на предыдущий элементы, то вставка элемента, как после, так и до заданного, реализуются без процедуры поиска. Приведем здесь примеры процедур вставки элементов до и после элемента с заданным номером. - 28 - procedure vstdo (i:integer); begin q2:=p2; while i<>0 do begin q2:=q2^.nextr; dec(i); end; new(r2); r2^.inf:=<информация о вставляемом элементе >; r2^.nextr:=q2; q2^.nextl^.nextr:=r2; r2^.nextl:=q2^.nextl; q2^.nextl:=r2; end; procedure vstpast (i:integer); begin q2:=p2; while i<>0 do begin q2:=q2^.nextr; dec(i); end; new(r2); r2^.inf:=<информация о вставляемом элементе >; r2^.nextr:=q2^.nextr; q2^.nextr:=r2; r2^.nextl:=q2; r2^.nextr^.nextl:=r2; end; Задания для самостоятельного выполнения 1. Записать процедуры удаления элементов из ЛДС, расположенных до и после заданного элемента. 2. Создать ЛДС из n элементов, информационные поля которых являются случайными целыми числами из некоторого диапазона. 3. Подсчитать количество и порядковые номера элементов ЛДС, значения информационных полей которых: 1) положительны; 2) отрицательны; 3) четны; 4) нечетны; 5) меньше заданного числа; - 29 - 6) больше заданного числа; 7) равны значению информационного поля k-го (1 k n) элемента; 8) меньше значения информационного поля k-го (1 k n) элемента; 9) больше значения информационного поля k-го (1 k n) элемента; 10) равны значению информационного поля предыдущего элемента; 11) равны значению информационного поля следующего элемента; 12) меньше значения информационного поля предыдущего элемента; 13) больше значения информационного поля предыдущего элемента; 14) меньше значения информационного поля следующего элемента; 15) больше значения информационного поля следующего элемента; 16) больше среднего значения информационных полей элементов; 17) меньше среднего значения информационных полей элементов. 18) равны наибольшему значению информационных полей элементов; 4. Удалить из ЛДС элементы, информационные поля которых удовлетворяют заданному условию из п.3. 5. Упорядочить оставшиеся элементы ЛДС в заданном порядке. §2. Бинарные деревья В задачах поиска информации широко используются нелинейные двусвязные списки - бинарные деревья, имеющие следующий вид: bt Бинарные деревья позволяют осуществлять эффективный поиск информации за счет её специального упорядочивания. Так, например, если необходимо найти заданный элемент в числовом массиве или убедиться в его отсут- 30 - ствии, то числа из массива следует распределить по вершинам дерева таким образом, чтобы каждое из них было больше числа, указанного в вершине по левой ссылке и меньше числа, указанного в вершине по правой ссылке. При этом для нахождения искомого числа или установления факта его отсутствия потребуется количество сравнений, превосходящее не более чем на единицу высоту бинарного дерева – наибольшую длину цепей из корня. При наличии N чисел в массиве поиска их всегда можно расположить по вершинам бинарного дерева таким образом, чтобы высота дерева не превосходила log2N . Такие бинарные деревья называются сбалансированными. При использовании ЛОС для хранения N чисел, упорядоченных по величине, для решения рассматриваемой задачи потребуется в среднем N/2 сравнений. Для оценки эффективности использования бинарных деревьев в задачах поиска информации создадим два типизированных файла случайных целых чисел из диапазона от 1 до 31, содержащих по 10031 число. Диапазон случайных чисел выбран равным количеству вершин в бинарном дереве высоты четыре. При создании указанных файлов в одном случае первую 31 компоненту введем с клавиатуры таким образом, чтобы эти числа можно было разместились по вершинам сбалансированного бинарного дерева так, что каждое из них было больше числа, указанного в вершине по левой ссылке и меньше числа, указанного в вершине по правой ссылке. В другом файле первые числа расположены монотонно, увеличиваясь от 1 до 31. При этом вместо бинарного дерева будет сформирован ЛОС. Для подсчета в каждом из этих файлов среднего числа сравнений можно воспользоваться процедурой btree с параметром именем обрабатываемого файла. procedure btree (nf:string); label 1,2,3; type s2=^el2; el2=record n:integer; nextr,nextl:s2; end; - 31 - var poisk:real; bt,qt,rt:s2; n:integer; begin assign(f,nf); reset(f); new(qt); read(f,qt^.n); qt^.nextr:=nil; qt^.nextl:=nil; bt:=qt; poisk:=0; 1: while not eof(f) do begin read(f,n); qt:=bt; 2:poisk:=poisk+1; if qt^.n=n then goto 1 else begin if qt^.n>n then if qt^.nextl<>nil then begin qt:=qt^.nextl; goto 2; end else begin new(rt); qt^.nextl:=rt; goto 3; end; else if qt^.nextr<>nil then begin qt:=qt^.nextr; goto 2; end else begin new(rt); qt^.nextr:=rt; end; 3:rt^.n:=n; rt^.nextl:=nil; rt^.nextr:=nil; end; end; writeln('средн. число сравн. = ', poisk/filesize(f):4:2); close(f); readln; end: - 32 - Задания для самостоятельного выполнения 1. Создать типизированный файл случайных целых чисел из диапазона от 1 до 31, содержащий 10031 число, первые 31 из которых равны соответственно 1, 2, …, 31. 2. Занумеровать вершины бинарного дерева высотой четыре таким образом, чтобы номер каждой вершины был больше номера, указанного в вершине по левой ссылке и меньше номера, указанного в вершине по правой ссылке. 3. Создать типизированный файл случайных целых чисел из диапазона от 1 до 31, содержащий 10031 число, первые 31 из которых вводятся с клавиатуры и соответствуют номерам вершин бинарного дерева, вводимым по слоям, начиная с корня дерева. 4. Провести вычислительный эксперимент по определению эффективности использования бинарных деревьев в задачах поиска информации. Для этого выполнить процедуру btree с каждым из созданных файлов. Сравнить и объяснить полученные результаты. Глава IV. Графы и динамические структуры данных §1. Способы задания графов При решении на компьютере задач на графах используются различные матричные и списковые способы задания. Матричные способы обладают большей избыточностью по сравнению со списковыми способами, но при этом возможно использование обширной библиотеки стандартных программ обработки матриц. Списковые способы, экономя память, требуют, обычно, разработки соответствующих программ. В зависимости от специфики решаемых задач при этом используются различные структуры данных. Рассмотрим здесь некоторые способы задания графов. Матричные способы задания Наиболее часто используются матрицы смежности и инциденций. Матрицы смежности обыкновенных неориентированных графов являются симметричными, бинарными с нулевой диагональю. Генерацию таких матриц смежности можно осуществить с помощью процедуры sozdmsm, использующей гло- 33 - бальные переменные a типа array [1..n0,1..n0] of byte, n(n n0) типа integer и датчик случайных чисел: procedure sozdmsm; begin randomize; for i:=1 to n do begin a[i,i]:=0; for j:=i+1 to n do begin a[i,j]:=random (2); a[j,i]:= a[i,j]; end; end; end; Генерацию матриц инцидентности произвольных неориентированных графов можно осуществить с помощью процедуры sozdminc, использующей глобальные переменные b типа array [1..n0,1..m0] of byte, n(n n0), m(m m0) типа integer и датчик случайных чисел: procedure sozdminc; var i,j:integer; begin randomize; for i:=1 to n do for j:=1 to m do b[i,j]:=0; for j:=1 to m do begin b[random(n)+1,j]:=1; b[random(n)+1,j]:=1; end; end; Вывести на экран матрицу инцидентности можно процедурой procedure prminc; begin for i:=1 to n do begin for j:=1 to m do write(b[i,j],' '); writeln; end; end; - 34 - Списковые способы задания Список ребер. Он представляет собой ЛОС из m элементов типа type sr=^elr; elr=record i,j:integer; next:sr; end;, на которые указывают переменные spr (на голову списка), qr и rr типа sr. Два информационных поля i и j содержат номера концевых вершин соответствующего ребра. Список ребер можно создать с помощью рекурсивной процедуры rsozdspr аналогичной процедуре создания ЛОС из элементов с одним информационным полем: procedure rsozdspr (m:integer); var v:integer; begin if m>0 then begin new(rr); v:= random(n)+1; rr^.i:=v; v:= random(n)+1; rr^.j:=v; rr^.next:=nil; qr^.next:=rr; qr:=rr; rsozdspr (m-1); end; end; Перед обращением к процедуре необходимо создать вспомогательный (нулевой) элемент ЛОС, выполнив команды new(spr); и qr:=spr;. Вывести на экран значения информационных полей списка ребер можно следующей процедурой: procedure prspr; begin qr:=spr^.next; while qr<>nil do begin write('(',qr^.i, ','); write(qr^.j, ')'); qr:=qr^.next; end; end; - 35 - Списки смежности. Граф с n вершинами задается следующей совокупностью списков: sps 0 ... 1 qs nil rs q, r ... n nil nil При этом ЛОС с номером i (1 i n) содержит число элементов равное степени i - ой вершины графа. Вертикальный список содержит n элементов, в каждый из которых включены по два ссылочных поля. При создании списков смежности вначале формируется ЛОС с элементами, содержащими по два ссылочных поля. Далее последовательно строятся ЛОС, каждый из которых содержит число элементов равное степени соответствующей вершины графа. На их головные элементы указывают свободные (правые) ссылочные поля элементов ЛОС с двумя ссылками. Описания типов элементов и имена соответствующих указателей: type ss=^els; els=record nextd:ss; nextr:s; end; s=^el; el=record - 36 - inf:integer; next:s; end; var sps,qs,rs:ss; q,r:s; Список элементов с двумя ссылочными полями можно создать рекурсивной процедурой: procedure rsozdsps (n:integer); begin if n>0 then begin new(rs); rs^.nextr:= nil; rs^.nextd:=nil; qs^.nextd:=rs; qs:=rs; rsozdsps (n-1); end; end; Перед обращением к этой процедуре необходимо создать нулевой элемент и установить на него указатели sps и qs с помощью команд new(sps); qs:=sps;. Информационные поля всех ЛОС, присоединяемых к построенному списку, можно заполнить для обыкновенных графов неповторяющимися случайными числами из диапазона от i+1 до n, где i - номер ЛОС. Число элементов в ЛОС тоже может быть случайным числом, не превосходящим n - i. Заполнить списки смежности можно и с клавиатуры по заданному графу с занумерованными вершинами, используя следующую процедуру: procedure sozdspsm(n:integer); var n1:integer; begin new(sps); qs:=sps; rsozdsps(n); qs:=sps^.nextd; for i:=1 to n do begin write(’степень вершины’, i ,’ =’); readln(n1); r:=nil; writeln(’ввод номеров вершин, смежных с’, i,’-ой’); - 37 - for j:=1 to n do begin new(q); read (q^.inf); q^.next:=r; r:=q; end; writeln; qs^.nextr:=q; qs:=qs^.nextd; end; Вывести на экран значения информационных полей списков смежности можно следующей процедурой: procedure prspsm; begin qs:=sps^.nextd; for i:=1 to nl do begin write(i, ': '); q:=qs^.nextr; rprlosn; qs:=qs^.nextd; writeln; end; end; Код Прюфера. Так по имени его автора называется слово n - буквенного алфавита длины n-2, позволяющее асимптотически оптимально кодировать помеченные n - вершинные деревья. Задать код Прюфера можно с помощью ЛОС, содержащего n-2 элемента, в информационных полях которых находятся случайные числа из диапазона от 1 до n. В качестве указателей на элементы ЛОС Прюфера используются статические переменные pruf, qp и rp типа sp: type sp=^elp; elp=record inf:integer; next:sp; end; var pruf,qp,rp:sp; Процедура создания кода Прюфера rsozdpruf совпадает по структуре с ранее рассмотренной процедурой rsozdlosn. Перед обращением к ней необходимо создать вспомогательный (нулевой) элемент, выполнив команды - 38 - new(pruf); и qp:= pruf;. Вывести на экран код Прюфера можно с помощью процедуры rprpruf аналогичной процедуре rprlosn; §2. Переход от одного способа задания к другому Поскольку графы могут задаваться по-разному, то при решении задач на графах возникает необходимость в переходе от одного способа задания к другому. Рассмотрим несколько примеров. Переход от матрицы смежности к списку ребер Для реализации такого перехода достаточно просмотреть область матрицы, расположенную над главной диагональю, добавляя в ЛОС ребер элементы, в информационных полях которых указываются номера строк и столбцов единичных элементов матрицы. Процедура msminspr создает ЛОС ребер от начала к концу, начиная с нулевого элемента: procedure msminspr; begin new(spr); qr:=spr; for i:=1 to n do for j:=i+1 to n do begin if a[i,j]=1 then begin new(rr); rr^.i:=i; rr^.j:=j; rr^.next:=nil; qr^.next:=r; qr:=rr; end; end; Структура программы перехода от матрицы смежности к списку ребер: program msmtospr; uses crt; const n0=10; type sr=^elr; elr=record i,j:integer; next:sr; end; var spr,qr,rr:sr; i,j,n:integer; a:array[1..n0,1..n0] of byte; procedure sozdmsm; (текст процедуры) - 39 - procedure prmsm; (текст процедуры) procedure msminspr; (текст процедуры) begin clrscr; randomize; write(’n(n n0)=’); readln(n); sozdmsm; prmsm; msminspr; writeln; prspr; readln; end. Переход от списка ребер к матрице инциденций Вначале необходимо создать нулевую матрицу инциденций с помощью процедуры: procedure sozd0minc; begin for i:=1 to n do for j:=1 to m do b[i,j]:=0; end; В каждом из m столбцов матрицы инцидентности может быть не более двух единичных элементов. Положим для простоты, что номера столбцов совпадают с номерами ребер в исходном списке ребер графа. Тогда в качестве номеров строк каждого столбца, в котором проставляются единицы, будут выступать значения информационных полей соответствующего элемента списка ребер. Этот алгоритм реализует следующая процедура: procedure sprinminc; begin qr:=spr^.next; j:=1; while qr<>nil do begin b[qr^.i,j]:=1; b[qr^.j,j]:=1; qr:=qr^.next; j:=j+1; end; end; - 40 - Структура программы перехода от списка ребер к матрице инцидентности: program sprtominc; uses crt; const n0=10;m0=20; type sr=^elr; elr=record i,j:integer; next:sr; end; var spr,qr,rr:sr; i,j,n,m:integer; b:array[1..n0,1..m0] of byte; procedure rsozdspr (m:integer); (текст процедуры) procedure prspr; (текст процедуры) procedure sozd0minc; (текст процедуры) procedure sprinminc; (текст процедуры) procedure prminc; (текст процедуры) begin clrscr; randomize; write(’n(n n0)=’); readln(n); write(’m(m m0)=’); readln(m); new(spr); qr:=spr; rsozdspr(m); prspr; writeln; sozd0minc; sprinminc; prminc; readln; end. Переход от списков смежности к списку ребер Необходимо последовательно просмотреть все ЛОС, присоединенные к списку ссылок, добавляя соответствующие элементы к списку ребер. При выделении в i – ом ЛОС (1 i n) элемента, информационное поле которого содержит число j, в список ребер добавляется элемент c информационными полями равными числам i и j. Рассмотренное преобразование можно выполнить следующей процедурой: procedure spsminspr; begin qs:=sps^.nextd; new(spr); qr:=spr;i:=1; while qs<>nil do - 41 - begin q:=qs^.nextr; while q<>nil do begin if q^.inf>=i then begin new(rr); rr^.i:=i; rr^.j:=qr^.inf; rr^.next:=nil; qr^.next:=rr; qr:=rr; end; q:=q^.next; end; qs:=qs^.nextd; i:=i+1; end; end; Структура программы перехода от списков смежности к списку ребер: program spsmtospr; uses crt; type s=^el; el=record inf:integer; next:s; end; ss=^els; els=record nextd:ss; nextr:s; end; sr=^elr; elr=record i,j:integer; next:sr; var q,r:s; sps,qs,rs:ss; spr,qr,rr:sr; i,j,n,m:integer; procedure rsozdsps (n:integer); (текст процедуры) procedure sozdspsm; (текст процедуры) procedure rprlosn; (текст процедуры) procedure prspsm; (текст процедуры) procedure spsminspr; (текст процедуры) - 42 - procedure prspr; (текст процедуры) begin clrscr; write(’n=’); readln(n); sozdspsm(n); writeln; prspsm; writeln; spsminspr; prsps; readln; end. Переход от кода Прюфера к списку ребер Для решения задачи потребуется процедура sozdlosnum, создающая вспомогательный ЛОС, содержащий номера вершин от 1 до n. procedure sozdlosnum (n:integer); begin for i:=1 to n do begin new(r); r^.inf:=i; r^.next:=nil; q^.next:=r; q:=r; end; end; При получении первых n-2 элементов списка ребер необходимо каждый раз искать в текущем (исходном) вспомогательном ЛОС минимальное число, которого нет в текущем (исходном) коде Прюфера. Проверка наличия в текущем коде Прюфера заданного числа может быть реализована процедурой: procedure anpruf (num:integer); label 1; begin qp:=pruf^.next; while qp<>nil do begin if qp^.inf=num then begin per:=1; goto 1; end; qp:=qp^.next; end; per:=0; j:=pruf^.next^.inf - 43 - rp:=pruf^.next; pruf^.next:=rp^.next; dispose(rp); 1:end; Если числа num нет в текущем коде Прюфера, то переключатель per устанавливается в 0 положение, значение информационного поля 1–го элемента запоминается в переменной j и этот элемент удаляется из кода Прюфера. Поиск в текущем вспомогательном ЛОС минимального числа, не встречающегося в текущем коде Прюфера, реализует процедура: procedure anlosnum; label 1; var a:integer; begin q:=p; while q^.next<>nil do begin a:=q^.next^.inf; anpruf (a); if per=0 then begin i:= a; r:=q^.next; q^.next:=r^.next; dispose(r); goto 1; end; q:=q^.next; end; 1: end; После окончания работы процедуры anlosnum в переменной i будет сформировано минимальное число, которого нет в текущем коде Прюфера. Элемент, хранивший это число, удаляется из вспомогательного ЛОС. Значения переменных i и j являются номерами концевых вершин очередного ребра, добавляемого в список ребер с помощью процедуры: procedure prufinspr; var k:integer; begin new(spr); qr:=spr;. for k:=1 to n-2 do begin anlosnum; new(rr); rr^.i:=i; rr^.j:=j; - 44 - rr^.next:=nil; qr^.next:=rr; qr:=rr; end; new(rr); rr^.i:=p^.next^.inf; rr^.j:=p^.next^.next^.inf; rr^.next:=nil; qr^.next:=rr; end; Последний (n-1)-й элемент ЛОС ребер содержит в информационных полях числа, указанные в двух оставшихся элементах вспомогательного ЛОС. Структура программы перехода от кода Прюфера к списку ребер: program pruftospr; uses crt; type sp=^elp; elp=record inf:integer; next:sp; end; s=^el; el=record inf:integer; next:s; end; sr=^elr; elr=record i,j:integer; next:sr; end; var pruf,qp,rp:sp; p,q,r:s; per:byte; spr,qr,rr:sr; i,j,n:integer; procedure rsozdpruf (n:integer); (текст процедуры) procedure rprpruf; (текст процедуры) procedure sozdlosnum (n:integer); (текст процедуры) procedure anpruf (num:integer); (текст процедуры) procedure anlosnum; (текст процедуры) procedure prufinspr; (текст процедуры) procedure prspr; (текст процедуры) - 45 - begin clrscr; randomize; write(’n=’); readln(n); new(pruf); qp:= pruf;. rsozdpruf(n-2); qp:= pruf^.next; rprpruf; writeln; new(p); q:=p; sozdlosnum(n); prufinspr; prspr; readln; end. Задания для самостоятельного выполнения Составить программу перевода: 1. Матрицы смежности в матрицу инцидентности. 2. Матрицы инцидентности в матрицу смежности. 3. Списка ребер в матрицу смежности. 4. Матрицы инцидентности в список ребер. 5. Списков смежности в список ребер. 6. Списка ребер в списки смежности. 7. Матрицы смежности в списки смежности. 8. Матрицы инцидентности в списки смежности. 9. Списков смежности в матрицу инцидентности. 10. Списков смежности в матрицу смежности. 11. Кода Прюфера в матрицу инцидентности. 12. Кода Прюфера в матрицу смежности. 13. Кода Прюфера в списки смежности. 14. Списка ребер дерева в код Прюфера. 15. Списков смежности дерева в код Прюфера. 16. Матрицы смежности дерева в код Прюфера. 17. Матрицы инцидентности дерева в код Прюфера. - 46 - Литература 1. Ахо А., Хопкрофт Дж., Ульман Дж.. Структуры данных и алгоритмы. – М.: Изд. дом «Вильямс», 2000. 2. Иорданский М.А., Рузанов В.А. Динамическое распределение памяти и обработка списков в системе Турбо-паскаль: методические рекомендации. – Нижний Новгород: НГПУ, 1995. 3. Павловская Т.А. Паскаль. Программирование на языке высокого уровня. – СПб.: Питер, 2006. 4. Стивенс Р. Delphi. Готовые алгоритмы. – М.: ДМК Пресс; СПб.: Питер, 2004. Интернет - ресурсы 5. http://www.comp-science.narod.ru/Progr_new/ 6. www.vspu.ac.ru/~chul/program/lection14.pdf 7. www.petrsu.ru/Chairs/IMO/Pascal/theory/ 8. algolist.manual.ru/ds/basic/ - 47 -