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

Рекурсия

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

7.4.4. Где использован тот факт, что граф не имеет циклов?

Решение. Мы опустили доказательство конечности глубины рекурсии. Для каждой вершины рассмотрим ее "глубину" - максимальную длину пути по стрелкам, из нее выходящего. Условие отсутствия циклов гарантирует, что эта величина конечна. Из вершины нулевой глубины стрелок не выходит. Глубина конца стрелки по крайней мере на 1 меньше, чем глубина начала. При работе процедуры \w{add(i)} все рекурсивные вызовы \w{add(j)} относятся к вершинам меньшей глубины.

Вернемся к оценке времени работы. Сколько вызовов \w{add(i)} возможно для какого-то фиксированного \w{i}? Прежде всего ясно, что первый из них печатает \w{i}, остальные сведутся к проверке того, что \w{i} уже напечатано. Ясно также, что вызовы \w{add(i)} индуцируются "печатающими" (первыми) вызовами \w{add(j)} для тех \w{j}, из которых в \w{i} ведет ребро. Следовательно, число вызовов \w{add(i)} равно числу входящих в \w{i} ребер (стрелок). При этом все вызовы, кроме первого, требуют O(1) операций, а первый требует времени, пропорционального числу исходящих из \w{i} стрелок. (Не считая времени, уходящего на выполнение \w{add(j)} для концов \w{j} выходящих ребер.) Отсюда видно, что общее время пропорционально числу ребер (плюс число вершин).

Связная компонента графа. Неориентированный граф - набор точек (вершин), некоторые из которых соединены линиями (ребрами). Неориентированный граф можно считать частным случаем ориентированного графа, в котором для каждой стрелки есть обратная.

Связной компонентой вершины \w{i} называется множество всех тех вершин, в которые можно попасть из \w{i}, идя по ребрам графа. (Поскольку граф неориентированный, отношение " \w{j} принадлежит связной компоненте \w{i} " является отношением эквивалентности.)

7.4.5. Дан неориентированный граф (для каждой вершины указано число соседей и массив номеров соседей, как в задаче о топологической сортировке). Составить алгоритм, который по заданному \w{i} печатает все вершины связной компоненты \w{i} по одному разу (и только их). Число действий не должно превосходить C\cdot{} (общее число вершин и ребер в связной компоненте).

Решение. Программа в процессе работы будет "закрашивать" некоторые вершины графа. Незакрашенной частью графа будем называть то, что останется, если выбросить все закрашенные вершины и ведущие в них ребра. Процедура \w{add(i)} закрашивает связную компоненту \w{i} в незакрашенной части графа (и не делает ничего, если вершина \w{i} уже закрашена).

procedure  add (i:1..n);
begin
| if вершина i закрашена then begin
| | ничего делать не надо
| end else begin
| | закрасить i (напечатать и пометить как закрашенную)
| | для всех j, соседних с i
| | | add(j);
| | end;
| end;
end;

Докажем, что эта процедура действует правильно (в предположении, что рекурсивные вызовы работают правильно). В самом деле, ничего, кроме связной компоненты незакрашенного графа, она закрасить не может. Проверим, что вся она будет закрашена. Пусть \w{k} - вершина, доступная из вершины \w{i} по пути {i}\rightarrow{j}\rightarrow\ldots\rightarrow{k}, проходящему только по незакрашенным вершинам. Будем рассматривать только пути, не возвращающиеся снова в \w{i}. Из всех таких путей выберем путь с наименьшим \w{j} (в порядке просмотра соседей в процедуре). Тогда при рассмотрении предыдущих соседей ни одна из вершин пути {j}\rightarrow\ldots\rightarrow{k} не будет закрашена (иначе \w{j} не было бы минимальным) и потому \w{k} окажется в связной компоненте незакрашенного графа к моменту вызова \w{add(j)}. Что и требовалось.

Чтобы установить конечность глубины рекурсии, заметим, что на каждом уровне рекурсии число незакрашенных вершин уменьшается хотя бы на 1.

Оценим число действий. Каждая вершина закрашивается не более одного раза - при первым вызове \w{add(i)} с данным \w{i}. Все последующие вызовы происходят при закрашивании соседей - количество таких вызовов не больше числа соседей - и сводятся к проверке того, что вершина \w{i} уже закрашена. Первый же вызов состоит в просмотре всех соседей и рекурсивных вызовах \w{add(j)} для всех них. Таким образом, общее число действий, связанных с вершиной \w{i}, не превосходит константы, умноженной на число ее соседей. Отсюда и вытекает требуемая оценка.

7.4.6. Решить ту же задачу для ориентированного графа (напечатать все вершины, доступные из данной по стрелкам; граф может содержать циклы).

Ответ. Годится по существу та же программа (строку "для всех соседей" надо заменить на "для всех вершин, куда ведут стрелки").

Следующий вариант задачи о связной компоненте имеет скорее теоретическое значение (и называется теоремой Сэвича).

7.4.7. Ориентированный граф имеет 2^n вершин (двоичные слова длины n ) и задан в виде функции есть_ребро, которая по двум вершинам x и y сообщает, есть ли в графе ребро из x в y. Составить алгоритм, который для данной пары вершин u и v определяет, есть ли путь (по ребрам) из u в v, используя память, ограниченную многочленом от n. (Время при этом может быть - и будет - очень большим.)

Указание. Использовать рекурсивную процедуру, выясняющую, существует ли путь из x в y длины не более 2^k (и вызывающую себя с уменьшенным на единицу значением k ).

Быстрая сортировка Хоара. В заключение приведем рекурсивный алгоритм сортировки массива, который на практике является одним из самых быстрых. Пусть дан массив {a[1]}\ldots{a[n]}. Рекурсивная процедура \w{sort(l,r:integer)} сортирует участок массива с индексами из полуинтервала ({l},{r}], то есть {a[l+1]}\ldots{a[r]}, не затрагивая остального массива.

procedure sort (l,r: integer);
begin
| if l = r then begin
| | ничего делать не надо - участок пуст
| end else begin
| | выбрать случайное число s в полуинтервале (l,r]
| | b := a[s]
| | переставить элементы сортируемого участка так, чтобы
| |   сначала шли элементы, меньшие b - участок (l,ll]
| |   затем элементы, равные b- участок (ll,rr]
| |   затем элементы, большие b       - участок (rr,r]
| | sort (l,ll);
| | sort (rr,r);
| end;
end;

Разделение элементов сортируемого участка на три категории (меньшие, равные, больше) рассматривалась в "Переменные, выражения, присваивания" (это можно сделать за время, пропорциональное длине участка). Конечность глубины рекурсии гарантируется тем, что длина сортируемого участка на каждом уровне рекурсии уменьшается хотя бы на 1.

< Лекция 6 || Лекция 7: 123456 || Лекция 8 >
Татьяна Новикова
Татьяна Новикова
Россия, Пошатово
Artem Bardakov
Artem Bardakov
Россия