Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pesni_o_Paskale_2013-11-15.pdf
Скачиваний:
45
Добавлен:
13.04.2015
Размер:
5.16 Mб
Скачать

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

узел, из которого мы пришли сюда по ходу расширения империи. Назовем это поле mPrev — предыдущий. Например, для узлов F и D предыдущим будет узел E.

Остроумный Ник додумался по ходу строительства империи убить ещё одного зайца: определить длину пути от любого узла до центра. Так одновременно решается задача о минимальном количестве пересекаемых границ, которую в 49-й главе он решал через массив множеств. В самом деле, к чему плодить две программы, если можно обойтись одной? Ведь в ходе постройки дерева обратных связей определить расстояния несложно. Достаточно при переходе к очередному узлу отмечать в нём расстояние к центру империи, — оно будет на единицу больше того, что хранится в предыдущем узле. В центре империи это расстояние равно нулю, а в остальных узлах будет таким, как показано на рис. 144.

 

3 H

 

 

 

B

3

 

 

3

 

 

2 G

I

C

2

 

 

 

 

A 2

F

1

0

D

E 1

Рис. 144 – Расстояния и пути от узлов графа к центру империи

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

Структура узла

Теперь уточним полезную нагрузку узла, что добавится в него? Во-первых, это упомянутый выше указатель на предыдущий узел mPrev — ниточка обратной связи. Во-вторых, надо застолбить поле для расстояния к центру империи, назовем

468

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

его mDist — «дистанция». Не забыть бы поле для окраски узла одним из трех цветов: белым, серым или черным. Назовем это поле mColor — «цвет», и будем хранить в нём одно из перечислимых значений цвета: White, Gray, Black (о перечислениях сказано в главе 32). В итоге проясняется следующая структура для узла графа:

type TColor = (White, Gray, Black); { Перечисление: белый, серый, черный }

TNode =

record

{ Запись для страны (узел графа) }

mName : Char;

{ Название страны (одна буква) }

mColor: TColor;

{ цвет узла, изначально белый }

mDist : integer;

{ длина пути к узлу, изначально -1 }

mPrev : PNode;

{ узел, из которого пришли в текущий }

mLinks: PLink;

{ список смежных узлов (ребер) }

mNext : PNode;

{ связь во вспомогательном списке }

end;

 

 

В рассыпную!

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

Программу P_58_1 построим на основе программы P_57_1, — из неё возьмем процедуру ввода графа и добавим ещё несколько подпрограмм. Две из них нужны для очереди, элементами которой будут узлы графа.

procedure PutInQue(arg: PNode);

function GetFromQue(var arg: Pnode): boolean;

Впрочем, для вас эти подпрограммы тоже не новы, — вспомните запись в танцевальный кружок в программе P_56_2. Там похожие процедуры применялись для очереди строк, а здесь организуется очередь узлов.

В начальный момент все вершины графа надо окрасить белым, — об этом позаботится простенькая процедура InitList. По-настоящему новой будет лишь процедура постройки империи Expand, вот её объявление.

procedure Expand(arg : PNode);

Она расширяет империю, начиная с заданного параметром arg узла. Алгоритм процедуры отвечает рассуждениям Ника, рассмотрим её подробней.

469

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

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

Далее следует цикл WHILE, он выполняется, пока очередь желающих войти в империю не опустеет. Выбрав из очереди функцией GetFromQue первый узел (в этот момент очередь опустеет, но ненадолго), пробегаем по списку его белых соседей, располагая там нужную информацию, перекрашивая соседей в серый цвет и помещая их в очередь. После этого извлеченный из очереди узел P очерняем и возвращаемся к началу цикла WHILE. Поскольку очередь узлов уже не пуста (добавились соседние узлы), функция GetFromQue выберет из неё следующий узел, и цикл WHILE выполнится вновь. В конце концов, белые узлы когда-то иссякнут. Тогда пополнение очереди прекратится, серые узлы постепенно будут выбраны из неё, очередь опустеет, и цикл WHILE завершится.

Вот, собственно и всё. Для наблюдения за экспансией империи в процедуру вставлены операторы печати, не влияющие на её работу (они подчеркнуты).

{ P_58_1 – Обход графа в ширину }

type PNode =

^TNode;

{ Указатель на запись-узел }

PLink =

^TLink;

{ Указатель на список связей }

TColor = (White, Gray, Black); { Перечисление для цветов узла }

TLink = record

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

mLink : PNode;

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

mNext : PLink;

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

end;

 

 

TNode = record

{ Запись для хранения страны (узел графа) }

mName : Char;

{ Название страны (одна буква) }

mColor: TColor;

{ цвет узла, изначально белый }

mDist : integer;

{ длина пути к узлу, изначально -1 }

mPrev : PNode;

{ узел, из которого пришли в данный }

mLinks: PLink;

{ список смежных узлов (указатели на соседей ) }

mNext : PNode;

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

end;

 

 

470

 

 

Глава 58

 

 

 

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

 

 

 

 

 

 

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);

var p: PLink;

 

begin

 

New(p);

{ создаем новую переменную-связь }

p^.mLink:= arg;

{ размещаем указатель на узел }

{ размещаем указатель в голове очереди }

p^.mNext:= Que;

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

Que:=p;

{ текущая запись в голове очереди }

end;

 

471

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

{ Извлечение из очереди указателя на узел } function GetFromQue(var arg: Pnode): boolean; var p, q: PLink;

begin

GetFromQue:= Assigned(Que); if Assigned(Que) then begin

{Поиск последнего элемента (хвоста) очереди } p:= Que; q:=p;

{если в очереди только один элемент, цикл не выполнится ни разу! } while Assigned(p^.mNext) do begin

q:=p;

{ текущий }

p:=p^.mNext;

{ следующий }

end;

 

 

{ p и q указывают на последний и предпоследний элементы }

arg:= p^.mLink;

 

 

if p=q

 

{ если в очереди был один элемент... }

then Que:= nil

 

{ очередь стала пустой }

else q^.mNext:= nil;

{ а иначе отцепляем последний элемент }

Dispose(p);

 

{ освобождаем память последнего элемента }

end;

end;

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

var p : PNode;

 

 

q : PLink;

 

 

begin

 

 

arg^.mDist:= 0;

{ расстояние до центра империи = 0

}

arg^.mColor:= Gray;

{ метим серым цветом }

 

PutInQue(arg);

{ и помещаем в очередь обработки }

 

while GetFromQue(p) do begin

{ извлекаем очередной узел }

Write(p^.mName, ' ->');

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

q:= p^.mLinks;

{ начинаем просмотр соседей }

while Assigned(q) do begin

if q^.mLink^.mColor = White then begin { если сосед ещё белый }

q^.mLink^.mColor:= Gray;

{ метим его серым }

q^.mLink^.mDist:= p^.mDist +1;

{ расстояние до центра }

q^.mLink^.mPrev:= p;

{ метим, откуда пришли }

PutInQue(q^.mLink);

{ и помещаем в очередь обработки }

Write(q^.mLink^.mName:2);

{ имя соседа – это для отладки }

end;

 

472

 

 

Глава 58

 

 

 

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

 

 

 

 

 

 

q:= q^.mNext;

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

 

 

end;

 

 

 

p^.mColor:= Black; { после обработки узла метим его черным }

 

 

Writeln;

{ новая строка – это для отладки }

 

end;

end;

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

procedure InitList; var p : PNode; begin

p:= List; { начинаем с головы списка узлов } { проходим по всем элементам списка }

while Assigned(p) do begin

p^.mColor:= White;

{

цвет узла изначально белый }

p^.mDist := -1;

{

длина пути к узлу изначально -1 }

p^.mPrev := nil;

{

узел, из которого пришли в данный }

p:= p^.mNext;

{ следующий узел }

end;

 

 

 

end;

 

 

 

var

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

 

C : Char;

{ название страны }

 

Start : PNode;

{ узел, с которого начинается расширение империи }

begin

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

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

List:= nil; Que:= nil;

 

Assign(F_In, 'P_57_1.in');

ReadData(F_In);

 

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

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

Write('Центр империи = '); Readln(C);

C:= UpCase(C);

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

Start:= GetPtr(C);

{ указатель на центр империи }

if Assigned(Start) then begin { если такая страна существует, }

InitList;

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

Expand(Start);

{ расширяем империю от центра Start }

end;

 

until false end.

473

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

В главной программе организован цикл, принимающий от пользователя исходную страну, из которой строится империя. Программа завершается при вводе любого символа, отличного от латинской буквы. Запустив программу, я ввел символ «E» и увидел на экране вот что:

E -> F D

F -> G A

D -> C

G -> I H

A -> B

C ->

I ->

H ->

B ->

Эти строки напечатаны операторами трассировки в процедуре Expand. Согласно первой строке из узла E мы попадаем в узлы F и D. Согласно второй — из узла F движемся в узлы G и A, и так далее. Последние четыре строки показывают, что узлы C, I, H и B оказались на окраинах империи, и продвижений оттуда нет. По этой трассировке нетрудно нарисовать дерево воображаемого продвижения купцов

(рис. 145).

3

H

 

 

 

 

 

 

 

 

 

B

3

 

 

 

 

 

2 G

I

3

 

2

 

C

 

 

 

 

 

 

A 2

F

1

D

E

1

 

0

Рис. 145 – Воображаемое продвижение купцов

474