В.Н.ПИНАЕВ ЛАБОРАТОРНЫЙ ПРАКТИКУМ ПО КУРСУ "СТРУКТУРЫ И

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ
РЫБИНСКАЯ ГОСУДАРСТВЕННАЯ АВИАЦИОННАЯ
ТЕХНОЛОГИЧЕСКАЯ АКАДЕМИЯ ИМЕНИ П. А. СОЛОВЬЕВА
КАФЕДРА МАТЕМАТИЧЕСКОГО И ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ЭЛЕКТРОННЫХ ВЫЧИСЛИТЕЛЬНЫХ СРЕДСТВ
В.Н.ПИНАЕВ
ЛАБОРАТОРНЫЙ ПРАКТИКУМ ПО
КУРСУ "СТРУКТУРЫ И
АЛГОРИТМЫ ОБРАБОТКИ ДАННЫХ
В ЭВМ"
части I–II
РЫБИНСК
2002
ББК 32. 973–01
П–32
УДК 681.3
Пинаев В.Н. Лабораторный практикум по курсу "Структуры и алгоритмы
обработки данных"/РГАТА. Рыбинск, 2002 г. – 23 с.
Лабораторный практикум содержит описание четырех лабораторных работ
по курсу "Структуры и алгоритмы обработки данных в ЭВМ". Каждая работа
снабжена теоретическим материалом, разобранными примерами и контрольными вопросами. Некоторые примеры иллюстрируются фрагментами описаний
структур и алгоритмов на языке ПАСКАЛЬ.
Практикум предназначен для студентов направления 552800 Информатика
и вычислительная техника и специальности 220400 Программное обеспечение
вычислительных вычислительной техники и автоматизированных систем.
© Пинаев В.Н., 2002
© Рыбинская государственная авиационная технологическая академия им. П. А. Соловьева, 2002
ЛАБОРАТОРНАЯ РАБОТА №1
МЕРА ИНФОРМАЦИИ
Начальные определения
В толковом словаре по вычислительным системам1 приводится следующее
определение:
“Энтропия (entropy) – мера количества информации, вырабатываемой
источником, попадающей к получателю в пересчете на символ (секунду и т.п.).
Понятие энтропии в теории информации введено К.Э. Шенноном в 1948 году и
позднее развито другими исследователями. … Энтропия (в зависимости от
выбранного основания логарифма, используемого в основной формуле) измеряется в битах, натах или Хартли. Термин «энтропия» взят по аналогии с энтропией в термодинамике, где она определяется выражением, имеющим ту
же форму (с точностью до множителя) и знака”.
Мы интерпретируем информацию как неопределенность, которую необходимо устранить, чтобы получить эту информацию.
Формула Шеннона имеет вид:
n
H    pi  log pi .
i 1
В частном случае при p1=p2=…=pn=1/n формула Шеннона носит название
формулы Хартли и имеет вид:
H  log n .
Здесь n – количество состояний системы, pi – вероятность перехрда системы в i-oe состояние.
ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ
1. Изучить теоретическую часть.
2. Составить программу, которая по заданному текстовому файлу определяет
меру информативности в пересчете на один символ. Имя входного файла
вводится с клавиатуры. Сформировать таблицу, в которой указать все
встречающиеся символы, их коды, частоту встречаемости в абсолютном и
относительном выражении. Таблицу и результирующий ответ вывести на
экран и в выходной файл. Дополнительно программа должна выводить сведения об авторе работы2.
КОНТРОЛЬНЫЕ ВОПРОСЫ И ЗАДАНИЯ
1. Как сформировать тестовый входной файл, чтобы итоговый результат был
равен некоторому наперед заданному целому положительному числу? КаТолковый словарь по вычислительным системам, Под ред. В. Иллингуорта и др. - М.: Машиностроение, 1990.
2
Это требование относится ко всем последующим лабораторным работам.
1
кое максимальное значение может иметь мера информативности на один
символ входного текста?
2. Каким типом целесообразно задавать диапазон индексов массива, где хранятся частоты встречаемых символов?
ЛАБОРАТОРНАЯ РАБОТА №2
КОДЫ ХАФФМАНА
Мера информативности (энтропия) была определена в лабораторной работе №1. На основе этого определения можно сформулировать алгоритм, позволяющий построить так называемый код Хаффмана.
Рассмотрим построение кода Хаффмана на примере.
Пусть имеется текст «Во поле береза стояла»3. Составим таблицу частот
символов этого текста.
Символ, В
п
о
л
е
б
р
з
а
с
т
я
Si
Частота, 1
1
3
2
3
3
1
1
1
2
1
1
1
Ci
1/21 1/21 3/21 2/21 3/21 3/21 1/21 1/21 1/21 2/21 1/21 1/21 1/21
Pi=Ci/n
Pi*Log2Pi
1
п
б
р
з
с
т
я
л
а
1
1
1
1
1
1
1
2
2
2
2
2
4
2
4
4
о
е
–
3
3
3
7
6
13
8
21
H=3,522572
n=
3
21
Символом “ ” здесь обозначен пробел между словами.
1
1
3
2
3
3
1
1
1
2
1
0,047619
-4,39232
-0,20916
0,047619
-4,39232
-0,20916
0,142857
-2,80735
-0,40105
0,095238
-3,39232
-0,32308
0,142857
-2,80735
-0,40105
0,142857
-2,80735
-0,40105
0,047619
-4,39232
-0,20916
0,047619
-4,39232
-0,20916
0,047619
-4,39232
-0,20916
0,095238
-3,39232
-0,32308
0,0
-4,
-0,
ЛАБОРАТОРНАЯ РАБОТА №3
ПРОСТЕЙШИЕ СТАТИЧЕСКИЕ СТРУКТУРЫ.
ДЕСКРИПТОР МАССИВА
Начальные определения
Под массивом в программировании понимается линейная структура данных фиксированного размера, элементы которой размещаются в оперативной
памяти последовательно. Массив характеризуется своим именем, размерностью, граничными парами и типом элемента. Элемент массива называется
слотом.
Дадим еще одно определение: массив – это совокупность однородных
элементов, имеющих одно общее имя и различающихся своими индексами.
Последовательное расположение элементов массива позволяет организовать прямой доступ к ним, то есть по индексам элемента вычисляется адрес его
расположения в памяти.
Адрес произвольного элемента массива определяется следующими характеристиками:
*
адресом первого элемента;
*
порядком размещения элементов в оперативной памяти;
*
размером памяти, занимаемой одним элементов.
Способ преобразования логической структуры массива в физическую последовательность элементов называется линеаризацией.
Чаще всего применяется линеаризация по строкам (элементы размещаются в памяти в порядке следования строк) или по столбцам. Иногда, имея ввиду
линеаризацию по строкам, говорят, что последний индекс изменяется быстрее,
чем первый. Аналогично, при линеаризации по столбцам быстрее изменяется
первый индекс.
Пример. Пусть задан трехмерный массив B[1..2,1..2,1..2]. При линеаризации его по строкам элементы в памяти будут располагаться в следующем порядке: B[1,1,1]; B[1,1,2]; B[1,2,1]; B[1,2,2]; B[2,1,1]; B[2,1,2]; B[2,2,1]; B[2,2,2].
При линеаризации по столбцам порядок будет таким: B[1,1,1]; B[2,1,1];
B[1,2,1]; B[2,2,1]; B[1,1,2]; B[2,1,2]; B[1,2,2]; B[2,2,2].
Вычисление адреса элемента
Рассмотрим частный случай n–мерного массива при n=1. Такой одномерный массив называется вектором. Пусть имеется вектор V[i..k] с длиной слота
L, и задан индекс j искомого элемента.
Тогда имеем:
Адрес(V[j]) = Адрес(V[i]) + L*(j – i) =
= Адрес(V[i]) – L*i + L*j.
Заметим, что первые два слагаемые этой суммы не зависят от индекса (j)
отыскиваемого слота и потому могут быть вычислены заранее. Нетрудно видеть, что эта постоянная величина в точности соответствует адресу нулевого
слота (чтобы в этом убедиться, достаточно подставить в выражение адреса j–го
элемента значение j=0). Правда, сам нулевой слот возможно и отсутствует в
массиве, но если бы он был, то располагался бы именно по такому адресу.
Итак, окончательно получаем:
Адрес(V[j]) = Адрес(V[0]) + L*j
Рассмотрим случай, когда n=2. Пусть задан массив V[i1..k1,i2..k2], и пусть
искомым является элемент V[j1,j2]. При линеаризации по строкам имеем:
Адрес(V[j1,j2])=Адрес(V[i1,i2])+
+L*((k2–i2+1)*(j1–i1)+(j2–i2)).
Раскрыв скобки и перегруппировав слагаемые, получим:
Адрес(V[j1,j2])= Адрес(V[i1,i2]) – L*(i1*(k2–i2+1)+i2)+ +L*(k2–i2+1)*j1+L*j2 =
Адрес(V[0,0])+D1*j1+D2*j2,
где D1, D2 – так называемые индексные множители, соответственно равные:
D1=L*(k2–i2+1) и D2=L; V[0,0] – нулевая компонента массива.
Целесообразность выделения постоянных слагаемых и множителей объясняется возможностью их предварительного вычисления и многократного в
дальнейшем использования.
Таким образом, для эффективного вычисления адреса слота массива
необходимо хранить некоторые заранее вычисленные значения.
Специальная информация, предназначенная для обеспечения корректной и
быстрой работы с элементами структуры данных, называется дескриптором
этой структуры.
Пусть задан произвольный n мерный массив со следующими граничными
парами: V[i1..k1, i2..k2, ... , in..kn] и пусть длина слота равна L. В этом случае
формула доступа принимает вид:
Адрес(V[j1,j2,...,jn]) = Адрес(V[0,0,...,0]) + D1*j1 +
+ D2*j2 +
... + Dn*jn.
Значение индексных множителей зависит от выбранного способа линеаризации.
При отображении строками индексные множители задаются рекуррентной
формулой:
Dn=L, Dm=(km+1–im+1+1)*Dm+1, при m=n–1,n–2,…, 1.
При отображении столбцами формула имеет вид:
D1=L, Dm=(km–1 – im–1+1)*Dm–1 при m = 2, 3, ..., n.
Возможная структура дескриптора массива представлена на таблице 1.
Таблица 1. Пример дескриптора массива
имя массива
адрес нулевого слова
адрес первого (начального) слота массива
размерность массива (n)
i1
k2
i2
k2
...
...
in
kn
D1
...
Dn
тип элемента
длина слота (L)
ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ
1. Изучить теоретическую часть.
2. Составить программу, которая по строке описания массива эмулирует его
размещение в выделенном участке оперативной памяти. Входные данные
вводятся с клавиатуры. Формат ввода выбрать самостоятельно. Реализовать
следующие действия: ввод данных, вывод дескриптора, обращение к произвольному элементу массива (чтение и запись), обращение к произвольному
байту выделенной памяти (чтение и запись), вывод сведений об авторе.
3. Используя построенный дескриптор, вычислить «вручную» адрес заданного
слота по его индексам.
КОНТРОЛЬНЫЕ ВОПРОСЫ И ЗАДАНИЯ
1. Дайте определения следующим терминам и понятиям: массив, слот, линеаризация, вектор, матрица, последовательное расположение в памяти, прямое
обращение по индексу, дескриптор.
2. Объясните назначение дескриптора и его составляющих.
3. Объясните, как выводится формула доступа.
4. Объясните, что такое нулевой слот.
5. Докажите формулы расчета адреса произвольного элемента для массива
размерности n.
6. Выведите формулу расчета адреса нулевого слота для массива размерности
n.
РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА
1. Костин А.Е., Шаньгин В.Ф. Организация и обработка структур данных в
вычислительных системах.
2. Разумов О.С. Организация данных в вычислительных системах. М.: Статистика, 1978.
ЛАБОРАТОРНАЯ РАБОТА №4
СТЕК. ФОРМЫ ЗАПИСИ АРИФМЕТИЧЕСКИХ И ЛОГИЧЕСКИХ
ВЫРАЖЕНИЙ. ПРИМЕНЕНИЕ СТЕКА ДЛЯ КОМПИЛЯЦИИ
ВЫРАЖЕНИЙ
Стек
Стек или как его еще называют – магазин – часто используется в программировании. Под стеком называется упорядоченный набор (список) элементов, в котором размещение новых элементов и удаление существующих
производятся только с одного конца, называемого вершиной стека. Возможны
различные реализации стека: динамическая и статическая.
В первом случае рост стека (добавление элементов) осуществляется за
счет выделения свободных элементов памяти. Такая организация предполагает
формирование связной структуры стека.
Статическая организация стека означает размещение его в заранее выделенной связной области памяти, например в массиве. В этом случае связь между элементами стека обеспечивается за счет последовательного размещения в
памяти элементов массива.
Операции, выполняемые над стеком, имеют специальные названия. При
добавлении нового элемента мы говорим, что элемент помещается в стек (операция push). Для стека s и элемента i можно определить операцию push(s,i).
Аналогично определяется операция выборки из стека pop(s), по которой из
стека удаляется верхний элемент и возвращается в качестве значения функции.
Пример 1. Связный стек может быть организован как список:
TYPE elem = record { звено стека }
inf:type_elem; {с информационным полем inf}
ref:next {и полем ссылки ref на следующее звено}
end;
next = ^elem;
VAR top:next; {указатель на вершину стека}
Пример 2. При использовании массива стек может быть определен следующим образом:
CONST n=100; {максимальный размер стека}
VAR stack:array[1..n] of type_elem;
top: 0..n; {указатель на занятый элемент}
Система польской записи
Система польской записи алгебраических выражений обеспечивает определенные преимущества по сравнению с традиционной, так называемой инфиксной записью. Различие между системами состоит в порядке записи операндов и знаков операций. Классификация форм записи выражений приводится
в следующей таблице.
Форма записи
инфиксная
(традиционная)
префиксная
(польская)
суффиксная
(обратная польская)
Принцип записи
Пример
Операнд1 Операция Операнд2
a + b * (c – d) / e
Операция Операнд1 Операнд2
+a*b/–cde
Операнд1 Операнд2 Операция
abcd–*e/+
Отметим, что выражения в префиксной и суффиксной формах являются
бесскобочными.
Прямая и обратная польская запись это форма задания выражений, удобная для организации вычисления значения выражения. Сформулируем алгоритм для вычисления значения выражения, заданного в обратной польской записи.
п1. Найти в выражении знак крайней слева операции.
п2. Выбрать два операнда, стоящих непосредственно слева от найденной
операции.
п3. Выполнить эту операцию.
п4. Заменить операнды и знак операции полученным результатом.
п5. Повторять вычисления с п1, до тех пор пока выражение не будет преобразовано в заключительный результат.
Пример. Рассмотрим выражение 5 + 8 / 2 * 3. Переведем его в обратную
польскую запись: 5 8 2 / 3 * +. В соответствии с алгоритмом выражение будет
преобразовываться следующим образом (подчеркнутым шрифтом в примере
выделяется выполняемая операция):
5 8 2 / 3 * +  5 8 2 / 3 * +  5 4 3 * +  5 12 +  17
Сформулированный алгоритм рассчитан только на использование двухместных операций. Несложно обобщить правило записи выражений в обратной
польской записи и их вычисления для случая использования многоместных
операций (в том числе и одноместных).
Рассмотрим вопрос корректности выражений. Для этого введем понятие
ранга для выражения в обратной польской записи.
Ранг любого операнда (константы или идентификатора) определим равным единице. Ранг операции равняется 1–n, где n количество операндов этой
операции. Например, умножение – двухместная операция, поэтому ее ранг равен 1–2 = –1.
ТЕОРЕМА. Польская суффиксная формула корректна тогда и только тогда, когда ранг этой формулы равен 1, а ранг любой ее левой части больше либо равен 1.
Пример. Вычислить ранг формулы A B C / D * +. Проанализируем формулу в порядке слева направо, указывая под ней изменяющееся значение счетчика
ранга:
Элементы формулы:
Значение счетчика ранга:
A B C / D * +
0 1 2 3 2 3 2 1
Следовательно, формула корректна.
Пример. Формула "A B C / + *" некорректна, так как ее ранг равен 0. Формула "A B C + + + D" некорректна, так как ранг ее левой части "A B C + + +"
меньше 1.
Преобразование инфиксных выражений в обратную польскую запись
Очередность выполнения операций инфиксного выражения задается их
приоритетами. Для решения нашей задачи определим в следующей таблице
ранги и приоритеты элементов формул.
Элементы формулы
Приоритет
Ранг
Операции: +, –
1
1
Операции: *, /
2
1
Операнды
3
1
Маркер конца: #
0
Символ "#" будет использоваться нами одновременно как символ конца
стека и входной (анализируемой) строки.
Для последующего алгоритма выберем следующие идентификаторы переменных:
R
– ранг формулы;
Top – указатель на вершину стека;
S[Top]
– элемент на вершине стека S;
P[i] – i ый символ выходной строки P;
NextChar– функция, возвращающая очередной символ входной строки;
N
– анализируемый символ входной строки;
Pr(N) – функция, определяющая приоритет символа N входной строки;
T
– символ (рабочая переменная);
Rang(T)
– функция, определяющая ранг символа T.
Ниже приводится алгоритм преобразования инфиксного выражения в обратную польскую запись.
п1.{ Инициализация стека } Top:=1; S[Top]:= '#''
п2.{ Начальные значения переменных } R:=0; i:=0
п3.{ Выбор первого символа входной строки } N:=NextChar
п4.{ Просмотр входной строки } Повторять п5 и п6 пока N<>'#'
п5.{ Удаление символов с большим или равным приоритетом из стека в выходную строку } Повторять пока Pr(N)Pr(S[Top])
п5.1. i:=i+1
п5.2. T:=S[Top]; Top:=Top–1
п5.3. P[i]:=T
п5.4. R:=R+Rang(T)
п5.5. {контроль корректности}если R<1, то перейти к п9.
п6.{ Размещение в стеке символа N и определение следующего символа }
п6.1. Top:=Top+1
п6.2. S[Top]:=N
п6.3. N:=NextChar
п7.{ Удаление оставшихся элементов из стека } Повторять пока S[Top] '#'
п7.1. i:=i+1
п7.2. T:=S[Top]; Top:=Top–1
п7.3. P[i]:=T
п7.4. R:=R+Rang(T)
п7.5. если R<1, то перейти к п9.
п8.Если R1, то перейти к п9, иначе закончить работу.
п9.Печатать текст "Выражение некорректно".
Пример. Перевести инфиксное выражение «A+B–C*D/E*H» в обратную
польскую запись. В следующей таблице приводятся изменяющиеся значения
основных величин алгоритма.
Анализируемый
символ
(N)
A
+
B
–
C
*
D
/
E
Действие алгоритма
инициализация
добавление в стек
выталкивание из стека и добавление в стек
добавление в стек
выталкивание из стека и добавление нового символа
добавление нового символа
выталкивание и добавление
добавление нового символа
выталкивание и добавление
добавление нового символа
Содержимое стека
(S)
Выходная строка
(P)
Ранг
(R)
#
#A
#+
A
1
#+B
#–
AB+
1
AB+C
2
AB+CD*
2
#–C
#–*
#–*D
#–/
#–/E
0
*
H
#
выталкивание и добавление
добавление нового символа
выталкивание символов из
стека
#–*
#–*H
#
AB + CD*E/
2
AB+CD*E/H*–
1
Преобразование инфиксного выражения, содержащего подвыражения в
скобках
За основу такого преобразования можно взять предыдущий алгоритм
(преобразование бесскобочного выражения). Для этого достаточно предусмотреть отдельную обработку скобочных символов. А именно: если очередной
символ N – левая скобка, то, не проверяя приоритетов, заносим ее в стек. После
занесения скобки припишем ей нулевой приоритет. Если очередной символ N –
правая скобка, то переписываем содержимое стека до ближайшей левой скобки
в выходную строку. После чего обе скобки отбрасываются.
Упражнение. Преобразовать в обратную польскую запись выражение
((A+B)*C–D)/E.
ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ
1. Изучить теоретическую часть.
2. Выполнить «ручную» компиляцию (преобразование) нескольких арифметических выражений, включая проверку корректности.
3. Вычислить значение выражений при выбранных самостоятельно значениях
переменных.
4. Составить программу преобразования арифметического выражения (целочисленные бинарные операции: сложение, вычитание, умножение, деление;
операнды – однолитерные идентификаторы) в инфиксной форме обратную
польскую запись, включая проверку его корректности. Предусмотреть возможность использования скобок. Реализовать следующие действия: ввод
инфиксного выражения, проверка корректности выражения, преобразование
инфиксного выражения в постфиксную форму записи, вычисление значения
выражения, вывод сведений об авторе.
ВОПРОСЫ И УПРАЖНЕНИЯ
1. Почему на этапе компиляции выражения, как правило, переводятся в постфиксную форму?
2. Сформулируйте алгоритм для вычисления значения выражения в префиксной форме.
3. Сформулируйте правило вычисления ранга и условие корректности префиксной формулы.
4. Приведите примеры одноместных и многоместных операций.
5. Запишите алгоритм преобразования произвольного арифметического выражения в обратную польскую запись.
6. Объясните необходимость использования стека при преобразовании выражения в обратную польскую запись.
7. Сформулируйте алгоритм проверки скобочной структуры арифметического
выражения.
РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА
1. Трамбле Ж., Соренсон П. Введение в структуры данных: пер. с англ.
М.:Машиностроение, 1982.
ЛАБОРАТОРНАЯ РАБОТА №5
ЛИНЕЙНЫЕ ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ.
ТОПОЛОГИЧЕСКАЯ СОРТИРОВКА
Линейные динамические структуры данных
Динамические структуры данных характеризуются тем, что в процессе работы количество элементов логической структуры постоянно меняется. Выбор
конкретной структуры данных определяется характером задачи, алгоритмом ее
решения и перечнем выполняемых операций. Поэтому, можно говорить, что
каждой структуре данных соответствует свой набор операций.
Для динамических структур данных характерны следующие особенности:
непостоянство размера занимаемой памяти, отсутствие физической смежности
для логически связанных элементов структуры, наличие механизма связи между логическими элементами, непрогнозируемые запросы на создание новых
элементов и ликвидацию старых, специальные меры по сборке "мусора".
Методы реализации динамических структур данных определяются выбранным языком программирования. В языках, не имеющих средств поддержки динамики, например, в Бейсике, пользователь вынужден размещать динамические структуры данных, как правило, в массивах. В этой ситуации на программиста ложится вся забота об эффективной организации таких структур и
методах работы с ними.
В языках с динамикой, например, в Паскале имеются специальные ссылочные типы данных, и программисту предоставлен набор процедур, обеспечивающих выделение памяти и ее возврат. Однако, сбор "мусора" зачастую не
реализуется. Располагая возможностью организации динамической структуры,
программист должен реализовать методы работы с нею.
Кроме перечисленных языков имеются языки уже сразу содержащие средства работы с динамическими структурами данных. К таким языкам, например,
можно отнести Пролог и Лисп.
К простейшим динамическим структурам относятся линейные структуры
списки. Под списком понимается такая логическая организация данных, когда
элементы упорядочены в линейном порядке. Каждый элемент несет некоторую
информацию и связан с последующим. Первый элемент структуры называется
головным. Последний элемент содержит признак конца списка.
В качестве примера рассмотрим задачу формирования и печати списка из
N элементов. В информационном поле разместим номер элемента. Программа
на Паскале будет иметь следующий вид:
Type ref = ^elem;
elem = record
inf : integer;
next : ref
end;
Var Head, T : ref;
N, i : integer;
Begin
readln(N); { количество элементов списка }
Head:=nil;
if N>0 then
begin
new(Head); { отдельно создаем первый элемент списка }
Head^.inf:=1;
T:=Head;
for i:=2 to N do
begin
{ создание всех последующих элементов списка }
new(T^.next);
T:=T^.next;
T^.inf:=i;
end;
T^.next:=nil
end;
{ список создан, приступаем к печати (не используя информации о
количестве элементов) }
T:=Head;
while T<>nil do
begin
writeln(T^.inf);
T:=T^.next
end
End.
Существуют различные типы списков: с заглавным элементом, односвязный, двухсвязный, кольцевой и т.п. По–прежнему, выбор логической структуры определяется характером задачи, алгоритмом и набором операций. Выбор
физической структуры определяется логической структурой и языком реализации.
Топологическая сортировка
Топологическая сортировка – это одна из известных задач в программировании, в которой широко применяются списки. Под топологической сортировкой понимается сортировка элементов, для которых определен частичный по-
рядок, то есть упорядочение задано не на всех, а только на некоторых парах
элементов.
Пример. В институтской программе изложение одних курсов опирается на
другие курсы. Скажем, для того, чтобы понять курс по структурам данных
необходимо прежде прослушать курс по конструированию программ и языкам
программирования. С другой стороны, на порядок изложения названных курсов никак не влияет курс по физике. Топологическая сортировка для набора
курсов означает определение такого порядка чтения курсов, при котором ни
один курс не читается раньше того, на материале которого он основан.
Частичный порядок элементов удобно представлять в графическом виде.
При этом каждому элементу (в нашем примере – курсу) соответствует некоторый узел графа, а зависимость (частичный порядок) задана направленными дугами. Ниже приводится пример задания частичной зависимости на множестве
восьми учебных дисциплин. В скобках обозначены их условные сокращения.
Структуры и
алгоритмы обработки данных
(С)
Конструирование
программ и языки
программирования
(К)
Теория выч.
процессов и
структур (Т)
Дискретная
математика
(Д)
Основы высшей математики (О)
Базы данных (Б)
Химия (Х)
Физика (Ф)
В нашем примере порядок может быть таким: О, Ф, К, Х, Д, С, Б, Т. Понятно, что этот вариант не является единственным. Более того, реальная задача
может оказаться гораздо более громоздкой. Возможны варианты, когда топологическая сортировка может вовсе не иметь решения.
Проверка найденного решения очень проста: достаточно в полученной последовательности провести дуги, связывающие частично упорядоченные элементы. Если все дуги будут направлены слева направо, то найденный порядок
допустим.
Если хотя бы одна дуга направлена в обратную сторону, то решение неверно.
Алгоритм топологической сортировки
Для упорядоченной пары элементов АВ элемент А будем называть
предшественником, а элемент В преемником. Суть алгоритма заключается в
следующем.
а) Для каждого элемента определяем его преемников и подсчитываем количество его предшественников. В дальнейшем работаем не с исходным графом, а с
множеством элементов, каждому из которых приписан счетчик предшественников и список преемников.
б) Выбираем из построенного множества и помещаем в выходной список элементы, не имеющие предшественников (с нулевым счетчиком).
в) Каждое извлечение элемента (см. пункт б) сопровождаем пересчетом количества оставшихся предшественников. Причем, эта операция затрагивает только те элементы, которые являются преемниками удаляемого.
Пункты б) и в) повторяем до тех пор пока это возможно. Если в очередной
раз пункт б) не выполним, а множество непусто, то топологическая сортировка
невозможна. Если же множество исчерпано, то топологическая сортировка выполнена "удачно" и в выходном списке перечислены элементы в допустимом
порядке.
По нашему алгоритму для каждого элемента необходимо хранить список
преемников и счетчик числа предшественников, поэтому представим наше
множество массивом по количеству элементов:
Const max=...; {количество элементов графа}
Type Ref : ^Elem;
Elem = record
N: 0..max; {количество предшественников или номер
элемента–преемника}
next: Ref { ссылка на список преемников }
end;
Var A : array [1..max] of record
name: string; {наименование элемента}
data: Elem;
end;
Причем поле N можно использовать трояко: как счетчик предшественников для данного элемента и как номер элемента, являющегося преемником
данного. Кроме того, для организации выборки элементов с нулевыми счетчиками, целесообразно такие элементы связать в список. Для этого можно вновь
задействовать поле N.
Пример. Выпишем связи из предыдущего примера: КС, КТ, КБ,
СТ, СБ, ДТ, ОФ, ОД, ОТ. Описанный выше массив A графически
будет выглядеть так, как изображено на рисунке ниже.
A[1]:
С
1

5

6

nil
A[2]:
К
0

1

5

6

nil
A[3]:
Ф
1

nil
A[4]:
О
0

3

8

5

nil
A[5]:
Т
4

nil
A[6]:
Б
2

nil
A[7]:
Х
0

nil
A[8]:
Д
1

5

nil
Здесь значения, проставленные в прямоугольниках, означают следующее.
Первый числовой столбец (1, 0, 1, 0, 4, 2, 0, 1) – счетчики числа предшественников. Все последующие числовые значения задают номера элементов, являющихся преемниками данного.
В начальном состоянии имеются три элемента: 2, 4, 7 – с нулевыми счетчиками. Свяжем их в отдельный список. Через Q будем обозначать указатель
на начальный элемент списка. Через R обозначим последний элемент. Определим типы:
Q, R : integer
Таким образом, информация об исходном частично упорядоченном множестве примет вид: Q=2, R=7. Для связывания элементов списка Q будем использовать опять же поле N. После чего наша структура примет вид:
A[1]:
С
1

5

6

nil
A[2]:
К
4

1

5

6

nil
A[3]:
Ф
1

nil
A[4]:
О
7

3
A[5]:
Т
4

nil
A[6]:
Б
2

nil
A[7]:
Х
0

nil
A[8]:
Д
1

5

8

nil

5

nil
Сейчас A[Q].N – это уже не счетчик, а указатель на следующий элемент
списка Q. Соответственно, A[R].N принимает нулевое значение, которое можно
трактовать как пустую ссылку. Случай, когда список Q пуст задается значениями Q=0, R=0.
Укрупненная блок схема алгоритма топологической сортировки выглядит
следующим образом:
п. 1. Инициализация массива A.
п. 2. Ввод исходных данных вида М  K.
п. 3. Подсчет количества предшественников и формирование списков преемников.
п. 4. Организация списка Q и задание его хвоста R
п. 5. L:= 0. { Количество элементов, выведенных на печать }
п. 6. Если Q=0, то переход на п13.
п. 7. Извлечение и печать первого элемента списка Q.
п. 8. L:=L+1
п. 9. Если L=max, то переход к п14.
п. 10. Просмотр преемников элемента с номером Q, пересчет числа предшественников этих элементов. Если у какого–либо элемента P число предшественников стало равным нулю, то включить данный элемент в список Q с
конца: A[R].data.N:=P; R:=P.
п. 11. Перестроение начала списка: Q:=A[Q].data.N
п. 12. Переход к п6.
п. 13. Вывод: «полное решение не существует»
п. 14. Конец работы.
ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ
1. Изучить теоретическую часть.
2. Выполнить топологическую сортировку на небольшом примере (8–10 элементов).
3. Проверить правильность выполненной сортировки.
4. Составить программу топологической сортировки. Предусмотреть возможность задания висячих вершин и кратных связей. Формат ввода выбрать самостоятельно.
ВОПРОСЫ И УПРАЖНЕНИЯ
1. Дайте определение линейной динамической структуры данных. Приведите
примеры таких структур. Назовите их характерные особенности.
2. Напишите процедуры формирования списочной структуры, поиска заданного элемента, вставки и удаления элемента, распечатки элементов списка,
слияния списков, подсчет длины списка.
3. Сформулируйте принцип топологической сортировки, приведите примеры.
Поясните необходимость использования тех или иных структур данных и
выбранный метод реализации.
РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА
1. Вирт Н. Алгоритмы + Структуры данных = программы: Пер. с англ. М.: Мир,
1985.
2. Трамбле Ж., Соренсон П. Введение в структуры данных: Пер. с англ.
М.:Машиностроение, 1982.
Download