Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi / песни о паскале.pdf
Скачиваний:
65
Добавлен:
26.03.2016
Размер:
5.16 Mб
Скачать

Глава 58 По графу шагом марш!

Сопоставьте это дерево с тем, что нацарапал на песке придворный программист (рис. 144). Разницы не заметит только слепой. В чем дело? Неужели вкралась ошибка?

Но, прежде чем огорчаться, сравните расстояния между центром империи и другими узлами — на обоих рисунках они совпадают. А это значит, что можно найти разные варианты кратчайших путей. Какой из них выберет программа — дело случая. Точнее, это определяется порядком ввода узлов. Мы знаем, что порядок строк входного файла не влияет на форму графа, но он влияет на выбор одного из кратчайших путей между узлами. Правда, купцам до этого дела нет, — ведь расстояния по таким путям будут одинаковыми.

Аты-баты

Теперь всё готово для создания полной версии программы. Пройдясь по графу вширь, мы разместили в узлах необходимые данные: расстояния от корня и обратные ссылки на пройденные узлы. Пора ставить победную точку — напечатать кратчайший путь между двумя узлами и длину этого пути.

Для постройки кратчайшего пути надо указать узел, из которого мы хотим попасть в центр империи. Двигаясь из него по цепочке обратных ссылок в направлении центра, мы, в конце концов, попадем в него. Значение обратной ссылки в центре империи равно NIL, что будет признаком окончания пути. С этой работой справится несложная функция MakePath — «создать путь».

function MakePath(arg : PNode): string;

В функцию передается узел, от которого надо вернуться к корню дерева, то есть к центру империи. Результатом будет строка пути вида «A à B à C».

Главную программу слегка дополним. Теперь пользователь введет названия двух стран, между которыми ищется кратчайший путь: «откуда» и «куда». Страну «откуда» назначим центром империи, а из страны «куда» будем возвращаться по цепочке обратных ссылок, — она составит параметр функции MakePath. Поскольку вводятся названия стран, а не указатели на них, преобразование имен в указатели тоже сделаем в главной программе.

Итак, в главной программе выполняются:

ввод графа из текстового файла;

ввод имен двух стран и преобразование их в указатели;

подготовка узлов графа – заполнение полей начальными значениями;

обход графа в ширину из заданного корня;

распечатка кратчайшего пути по цепочке обратных ссылок.

475

Глава 58 По графу шагом марш!

Все действия, кроме первого, зациклим, — тогда пользователь сможет задавать для этого графа разные сочетания стран. Признаком выхода из цикла будет ввод любого символа, отличного от латинской буквы. Надеюсь, что сказанного достаточно, чтобы разобраться в программе P_58_2. Эта программа включает части программ P_57_1 и P_58_1, которые я лишь обозначил.

{ P_58_2 – Поиск кратчайшего пути и определение расстояний в графе } type { Описания типов взять из P_58_1 }

var List :

PNode;

{

список всех стран континента

}

Que :

PLink;

{

очередь присоединяемых узлов

}

{ Функция поиска страны (узла графа) по имени страны } function GetPtr(aName : char): PNode;

{Взять из P_57_1 } end;

{Функция создает новую страну (узел) } function MakeNode(aName : Char): PNode;

{Взять из P_57_1 }

end;

{ Процедура установки связи узла p1 с узлом p2 } procedure Link(p1, p2 : PNode);

{ Взять из P_57_1 } end;

{ Процедура чтения графа из текстового файла } procedure ReadData(var F: Text);

{Взять из P_57_1 } end;

{Помещение указателя на узел в глобальную очередь Que } procedure PutInQue(arg: PNode);

{Взять из P_58_1 }

end;

{ Извлечение из очереди указателя на узел }

function GetFromQue(var arg: Pnode): boolean;

{ Взять из P_58_1 }

end;

476

Глава 58 По графу шагом марш!

{ Инициализация списка узлов перед постройкой империи }

procedure InitList;

{Взять из P_58_1 } end;

{Процедура расширения (экспансии) империи, начиная с заданного узла arg } procedure Expand(arg : PNode);

{Взять из P_58_1,

подчеркнутые там операторы для трассировочной распечатки удалить } end;

{ Функция для формирования пути от центра империи к заданному узлу }

function MakePath(arg : PNode): string; var p : PNode;

S : string;

 

 

begin

 

 

 

S:= arg^.mName;

{ имя конечного узла }

p:= arg^.mPrev;

{ указатель на предыдущий узел }

while Assigned(p) do begin

{ пока не достигли корня }

S:= p^.mName +' -> '+ S;

{ добавляем к пути имя узла }

p:= p^.mPrev;

 

{ переход к следующему узлу }

end;

 

 

 

MakePath:= S;

 

 

end;

 

 

 

var

F_In {, F_Out} : Text; { входной и выходной файла }

 

C1, C2 : Char;

 

{ названия стран "откуда" и "куда" }

 

Start, Stop : PNode;

{ узлы "откуда" и "куда" }

begin

{--- Главная программа ---}

{ Инициализация списка узлов и очереди узлов }

List:= nil; Que:= nil;

 

Assign(F_In, 'P_57_1.in');

 

ReadData(F_In);

 

{ чтение графа }

{ Цикл ввода названий стран } repeat

Write('Откуда= '); Readln(C1);

C1:= UpCase(C1);

if not (C1 in ['A'..'Z']) then break; Write('Куда = '); Readln(C2);

C2:= UpCase(C2);

if not (C2 in ['A'..'Z']) then break;

477

 

 

Глава 58

 

 

 

По графу шагом марш!

 

 

 

 

 

 

Start:= GetPtr(C1);

{ начальный узел }

 

 

Stop:= GetPtr(C2);

{ конечный узел }

 

if Assigned(Start) and Assigned(Stop) then begin

{ если такие страны

существуют, }

InitList;

{ устанавливаем начальные значения в полях узлов }

Expand(Start);

{ расширяем империю от узла Start }

Writeln (Stop^.mDist:3, ’’:3, MakePath(Stop)); end;

until false end.

И вот настал час испытаний, вводим данные и получаем это:

Откуда: E

Куда: H

3 E -> F -> G -> H

Ура! Заработало! Сколько труда за этой короткой строчкой! Оправданы ли наши усилия? Конечно! Истина дорого дается, но теперь мы не заблудимся даже в многотысячном графе!

Графы — это мощный инструмент для решения широкого круга инженерных задач. Их применяют при сортировке и поиске данных (здесь используют деревья), в расчетах электрических схем и потоков жидкостей, — всё не перечислить. Мы отщипнули лишь краешек этого каравая, вы можете узнать о графах больше по книгам [9] и [17] из списка рекомендуемой литературы.

Итоги

Обход графа в ширину — это один из базовых алгоритмов обработки графов. На нём основано решение многих задач, в том числе – поиск кратчайшего пути между узлами.

При решении задач на графах в его узлах размещают информацию, нужную для решения данной задачи. Иногда такую информацию размещают и в ребрах, для этого в структуру ребер вводят необходимые поля.

Аслабо?

А) Изобразите граф, содержащий не менее 20 вершин, обозначьте вершины латинскими буквами и поищите в этом графе кратчайшие пути программой

P_58_2.

Б) Пусть узлы графа — это города, а ребра — дороги между ними. Расстояния между городами разные и они известны. Как отразить в структуре графа эти расстояния? Предложите что-нибудь.

478

Глава 58 По графу шагом марш!

В) Пусть расстояния между городами указаны в поле mDist записи TLink.

TLink =

record

{ Тип список связей }

mLink

: PNode;

{ указатель на смежный узел }

mDist

: integer;

{ расстояние между городами }

mNext : PLink;

{ указатель на следующую запись в списке }

end;

 

 

 

 

 

Предложите формат входного файла, содержащего в числе прочего расстояния между городами.

Г) Пусть выбран следующий формат входного файла, содержащий расстояния между городами (приведена одна строка).

A C 20 E 40

Здесь первый символ, как и ранее, обозначает текущий узел. Затем перечисляются его соседи с указанием расстояний до них. Например, между узлами A и C 20 км, а между узлами A и E — 40 км. Напишите процедуру ввода графа из такого файла.

Д) Напишите программу для поиска кратчайшего пути с учетом расстояний между городами. Подсказка: измените процедуру обхода в ширину так, чтобы серый кандидат исследовал всех соседей (не только белых), проверяя в них поле расстояния mDist. Если путь к соседу через кандидата окажется короче того, что уже отмечен в соседе, то следует изменить как расстояние, так и обратную ссылку в соседе. Вдобавок если сосед не серый, он ставится в очередь.

Е) Предположим, что купцам интересны не расстояния между столицами, а размер пошлин, вносимых при пересечении границ. Эти пошлины зависят от пересекаемой границы (то есть от пары стран). Годится ли для этого случая рассмотренная выше модель с разными расстояниями между городами?

Ж) С некоторых пор купцы учредили свой ежегодный съезд — Континентальный Купеческий Конгресс, где обсуждали свои проблемы. Каждая страна отправляла на съезд по одному делегату, а расходы на пересечение границ (визы) оплачивались из общей кассы. Посчитайте эти расходы (1 пиастр за каждое пересечение), если известна страна проведения конгресса. Учтите, что купцы следовали на съезд кратчайшими маршрутами.

З) Напишите программу для определения страны, где можно провести съезд с наименьшими издержками (см. задачу Ж).

И) Решите задачи Ж и З для случая разной стоимости виз на границах.

479

Соседние файлы в папке delphi