Итак, в главной программе выполняются:
• ввод графа из текстового файла;
• ввод имен двух стран и преобразование их в указатели;
• подготовка узлов графа – заполнение полей начальными значениями;
• обход графа в ширину из заданного корня;
• распечатка кратчайшего пути по цепочке обратных ссылок.
Все действия, кроме первого, зациклим, – тогда пользователь сможет задавать для этого графа разные сочетания стран. Признаком выхода из цикла будет ввод любого символа, отличного от латинской буквы. Надеюсь, что сказанного достаточно, чтобы разобраться в программе «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;
{ Инициализация списка узлов перед "постройкой империи" }
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;
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] из списка рекомендуемой литературы.
Итоги
• Обход графа в ширину – это один из базовых алгоритмов обработки графов. На нём основано решение многих задач, в том числе – поиск кратчайшего пути между узлами.
Читать дальше