Опубликован: 08.04.2009 | Уровень: для всех | Доступ: платный
Лекция 7:

Рекурсия

< Лекция 6 || Лекция 7: 123456 || Лекция 8 >

7.4. Другие применения рекурсии

Топологическая сортировка. Представим себе n чиновников, каждый из которых выдает справки определенного вида. Мы хотим получить все эти справки, соблюдая установленные ограничения: у каждого чиновника есть список справок, которые нужно собрать перед обращением к нему. Дело безнадежно, если схема зависимостей имеет цикл (справку A нельзя получить без B, B без C,\ldots,Y без Z и Z без A ). Предполагая, что такого цикла нет, требуется составить план, указывающий один из возможных порядков получения справок.

Изображая чиновников точками, а зависимости - стрелками, приходим к такой формулировке. Имеется n точек, пронумерованных от 1 до n. Из каждой точки ведет несколько (возможно, 0 ) стрелок в другие точки. (Такая картинка называется ориентированным графом.) Циклов нет. Требуется расположить вершины графа (точки) в таком порядке, чтобы конец любой стрелки предшествовал ее началу. Эта задача называется топологической сортировкой.

7.4.1. Доказать, что это всегда возможно.

Решение. Из условия отсутствия циклов вытекает, что есть вершина, из которой вообще не выходит стрелок (иначе можно двигаться по стрелкам, пока не зациклимся). Ее будем считать первой. Выкидывая все стрелки, в нее ведущие, мы сводим задачу к графу с меньшим числом вершин и продолжаем рассуждение по индукции.

7.4.2. Предположим, что ориентированный граф без циклов хранится в такой форме: для каждого \w{i} от \w{1} до \w{n} в \w{num[i]} хранится число выходящих из \w{i} стрелок, в {adr[i][1]},\ldots, {adr[i][num[i]]} - номера вершин, куда эти стрелки ведут. Составить (рекурсивный) алгоритм, который производит топологическую сортировку не более чем за C\cdot({n}+{m}) действий, где \w{m} - число ребер графа (стрелок).

Замечание. Непосредственная реализация приведенного выше доказательства существования не дает требуемой оценки; ее приходится немного подправить.

Решение. Наша программа будет печатать номера вершин. В массиве

printed: array[1..n] of boolean

мы будем хранить сведения о том, какие вершины напечатаны (и корректировать их одновременно с печатью вершины). Будем говорить, что напечатанная последовательность вершин корректна, если никакая вершина не напечатана дважды и для любого номера \w{i}, входящего в эту последовательность, все вершины, в которые ведут стрелки из \w{i}, напечатаны, и притом до \w{i}.

procedure add (i: 1..n);
| {дано: напечатанное корректно;}
| {надо: напечатанное корректно и включает вершину i}
begin
| if printed [i] then begin {вершина i уже напечатана}
| | {ничего делать не надо}
| end else begin
| | {напечатанное корректно}
| | for j:=1 to num[i] do begin
| | | add(adr[i][j]);
| | end;
| | {напечатанное корректно, все вершины, в которые из
| |  i ведут стрелки, уже напечатаны - так что можно
| |  печатать i, не нарушая корректности}
| | if not printed[i] then begin
| | | writeln(i); printed [i]:= TRUE;
| | end;
| end;
end;

Основная программа:

for i:=1 to n do begin
| printed[i]:= FALSE;
end;
for i:=1 to n do begin
| add(i)
end;

К оценке времени работы мы вскоре вернемся.

7.4.3. В приведенной программе можно выбросить проверку, заменив

if not printed[i] then begin
| writeln(i); printed [i]:= TRUE;
end;

на

writeln(i); printed [i]:= TRUE;

Почему? Как изменится спецификация процедуры?

Решение. Спецификацию можно выбрать такой:

дано: напечатанное корректно
надо: напечатанное корректно и включает вершину i;
      все вновь напечатанные вершины доступны из i.
< Лекция 6 || Лекция 7: 123456 || Лекция 8 >
Татьяна Новикова
Татьяна Новикова
Россия, Пошатово
Artem Bardakov
Artem Bardakov
Россия