- •А.Н. Горитов
- •Учебное пособие
- •Учебное пособие
- •Введение
- •1 Введение в предмет
- •1.1 Непрерывная и дискретная информация
- •1.2 Данные и эвм
- •1.3 Объекты предметной области
- •1.4 Представление информации об объектах
- •1.5 Абстрактные алфавиты. Кодирование
- •2 Основные типы и структуры данных эвм
- •2.1 Архитектурные особенности эвм, наиболее существенные для представления данных
- •2.2 Основные понятия о типах и структурах данных
- •2.3 Массивы
- •2.4 Строки
- •2.5 Записи
- •2.6 Записи с вариантами
- •2.7 Множества
- •3 Последовательный файл
- •3.1 Основные свойства последовательных файлов
- •3.2 Сортировка последовательных файлов
- •4 Полустатические структуры
- •4.1 Стек, очередь и дек как полустатические структуры
- •4.2 Представление полустатических структур с помощью массивов
- •5 Линейные динамические структуры
- •5.1 Основные свойства динамических структур
- •5.2 Реализация связного списка массивом
- •5.3 Кольцевой связный список
- •5.4 Линейный двусвязный список
- •6 Представление динамических структур с помощью указателей
- •6.1 Указатели
- •6.2 Представление стека
- •6.3 Представление очереди
- •6.4 Ведение динамических списков с помощью указателей
- •6.5 Алгоритм составления кольцевого двусвязного списка
- •7 Древовидные структуры данных
- •7.1 Основные понятия и определения
- •7.2 Представление деревьев в эвм
- •7.3 Основные операции с бинарными деревьями
- •7.4 Сильно ветвящиеся деревья
- •8 Алгоритмы на графах
- •8.1 Машинное представление графов
- •8.2 Поиск в глубину в графе
- •8.3 Поиск в ширину в графе
- •8.4 Стягивающие деревья (каркасы)
- •8.5 Отыскание фундаментального множества циклов в графе
- •8.6 Эйлеровы пути в графе
- •8.7 Алгоритмы с возвратом
- •8.8 Нахождение кратчайших путей в графе
- •8.9 Кратчайшие пути от фиксированной вершины
- •8.10 Алгоритм Дейкстры
- •8.11 Пути в бесконтурном графе
- •Литература
8.7 Алгоритмы с возвратом
Рассмотрим теперь задачу нахождения путей, проходящих ровно один раз через каждую вершину (а не через каждое ребро) графа. Путь с таким свойством называется гамильтоновым путем, а цикл – гамильтоновым циклом. Пример гамильтонова пути в графе и графа, в котором такого пути не существует, показан на рис. 31. Несмотря на кажущееся сходство задач, пути их решения совершенно различны.
а) Гамильтонов путь в графе б) Гамильтонов путь не существует
Рис. 31 Различные типы графов
В отличие от эйлеровых путей для гамильтоновых путей не известно ни одного простого необходимого и достаточного условия существования. Не известен также алгоритм, который проверял бы существование га-мильтонова пути в произвольном графе, используя число шагов, ограниченное многочленом от п (количество вершин в графе).
Очевидным алгоритмом нахождения гамильтонова пути в графе является полный перебор всех возможностей: генерируем все п! различных последовательностей вершин и для каждой проверяем, определяет ли она гамильтонов путь. Такие действия потребуют по крайней мере п!п шагов, а такая функция растет гораздо быстрее, чем произвольный многочлен и даже быстрее, чем экспоненциальная функция вида ап (а>1).
Однако существует общий метод, позволяющий значительно сократить число шагов в алгоритмах типа полного перебора. Для применения этого метода искомое решение должно иметь вид последовательности
< xj,..., x > . Идея метода состоит в следующем.
Строим наше решение последовательно, начиная с пустой последовательности, т.е. последовательности длины 0. Имея некоторое частичное решение < xj,..., xi >, стараемся найти такое допустимое значение xi+1,
относительно которого мы не можем сразу заключить, что < xj,..., xi, xi+1 > можно расширить до некоторого решения (а может быть < xj,...,xi,xi+1 > уже является решением). Если такое предполагаемое, но еще не использованное значение xi+i существует, то мы добавляем его к нашему частичному решению и продолжаем процесс для последовательности < xj,..., xi, xi+1 > . Если его не существует, то мы возвращаемся к частичному решению < xj,..., xi _j > и продолжаем наш процесс, отыскивая новое, еще не использованное допустимое значение xi/ . Такие алгоритмы называются алгоритмами с возвратом (backtracking).
Точнее говоря, мы предполагаем, что для каждого к > 0 существует некоторое множество Ак, из которого мы будем выбирать кандидатов для k-й координаты частичного решения. Очевидно, что множества Ак должны быть определены таким образом, что для каждого целочисленного решения < xj,..., xn > нашей задачи и для
каждого k<n множество Ак содержало бы элемент xk (на практике Ак может содержать и "лишние" элементы,
не появляющиеся в к-й координате ни одного целочисленного решения).
Предполагаем также, что существует некоторая простая функция Р, которая произвольному частичному решению < xj,..., xi > ставит в соответствие
P\x\,..., x i) = если последовательность < xj,..., x i > нельзя
false, расширить до решения и
P(xj,...,xi) = если значение xi допустимо для частичного
true' решения < xj,..., x i_j > . Это не означает, что
< xj,..., x i_i > обязательно расширяется до
полного решения. Этот процесс можно записать в виде следующей схемы алгоритма BEGIN k := 1; WHILE k > 0 DO
IF существует еще неиспользованный элемент ye Ak,
такой, что Р(х[1], ..., x[k-l], у) THEN BEGIN
x[k] := у; {элемент у использован} IF <х[1], ..., х[к]> является целочисленным решением THEN write(x[l], ..., х[к]); к:=к+1; END
ELSE {возврат на более короткое частичное решение; все элементы множества Ак вновь становятся неиспользованными} к:=к-1 END. Этот алгоритм находит все решения в предположении, что множества Ак - конечны, и что существует п
такое, что P\x\ -, ■ ■ ■ -, xi) = i^e для всех xj Е Ay,..., xn Е An . Последнее условие означает, что
все решения имеют длину меньше п.
Можно доказать и более общее свойство, а именно.
Пусть S > 0 и пусть < Xj,...,X j > - некоторое частичное решение, построенное алгоритмом. Тогда, начиная с итерации цикла 3, для которого k = s, X[i] = хъ 1 < i < s, алгоритм генерирует все целочисленные решения, являющиеся расширением последовательности < Xj,..., Xs_^ >, и приходит к состоянию, когда k = s -
1.
Последнее свойство приводит непосредственно к рекурсивному варианту алгоритма с возвратом.
1 PROCEDURE AP(k);
{генерирование всех решений, являющихся расширением последовательности Х[1], ..., Х[к-1]; массив X - глобальный}
2 BEGIN
FOR yeАк такого, что Р(х[1], ..., х[к-1], у) DO
BEGIN Х[к] := у;
IF х[1], ..., Х[к] есть целочисленное решение
THEN write(X[l], ..., Х[к]);
АР(к+1)
8 END
9 END;
Применим алгоритм с возвратом для нахождения гамильтонова цикла в графе G = <V, Е>. Каждый такой цикл можно представить последовательностью < Xj,X2,...,xw+1 > такой, что Xj = хи+1 = Vq , где Vg- некоторая фиксированная вершина графа и хг ^ X, для 1 < / < j <п. В соответствии с этим можно положить
Ak=V.
Граф G = <V, Е> представим списками инцидентности СПИСОК[v], ve V. Тогда процедура для нахождения всех гамильтоновых циклов, являющихся расширением последовательности < Xj,..., x^._j > :
PROCEDURE гамильт(К); {массив X - глобальный}
BEGIN
3 FOR уеСПИСОК[х[к-1]] DO
IF (k = n+l) and (y = vO)
THEN write(x[l], ..., x[n], vO)
ELSE IF DOP[y] THEN
7 BEGIN
x[k] := y; DOP[y] := false;
гамильт(к+1);
DOP[y] := true
11 END
12 END;
Главная программа: BEGIN
FOR veV DO DOP[v] := true; {инициализация}
X[l] := vO; {vO - произвольная фиксированная вершина графа}
DOP[v0] := false;
гамильт(2) END.
Следует отметить, что для большинства приложений число шагов алгоритма с возвратом хотя и будет меньше, чем в случае полного перебора, однако же в наихудшем растет по экспоненте с возрастанием размерности задачи. Это справедливо и для случая, когда мы ищем только одно, а не все решения.