перебор с возвратом

advertisement
Перебор с возвратом
Баронов Антон
Введение
Во многих задачах из различных областей знания
ставятся вопросы-задания типа: “Сколько
существует способов …”, “Подсчитайте количество
элементов …”, “Перечислите все возможные
варианты …”, “Есть ли способ …”, “Существует ли
объект…” и т. п. Ответы на них, как правило,
требуют исчерпывающего поиска в некотором
множестве M всех возможных вариантов, среди
которых находятся решения конкретной задачи.
Один из общих методов организации
исчерпывающего поиска: перебор с возвратом
(backtracking).
Введение
Решение задачи методом перебора с возвратом
строится конструктивно последовательным
расширением частичного решения. Если на
конкретном шаге такое расширение провести не
удается, то происходит возврат к более короткому
частичному решению, и попытки его расширить
продолжаются. Для ускорения перебора с
возвратом вычисления всегда стараются
организовать так, чтобы была возможность
отметать как можно раньше и как можно больше
заведомо неподходящих вариантов M. Перебор с
возвратом также можно рассмотреть как задачу об
обходе дерева решений.
Вычислительная схема






Сформулируем задачу обхода произвольного дерева.
Будем считать, что у нас имеется Робот, который в
каждый момент находится в одной из вершин дерева
(вершины изображены на рисунке кружочками). Он
умеет выполнять команды:
вверх_налево (идти по самой левой из выходящих
вверх стрелок)
вправо (перейти в соседнюю справа вершину)
вниз (спуститься вниз на один уровень)
и проверки, соответствующие возможности выполнить
каждую из команд, называемые
"есть_сверху"
"есть_справа"
"есть_снизу"
Вычислительная схема
У Робота есть команда "обработать" и его задача - обработать все листья (вершины, из
которых нет стрелок вверх, то есть где условие "есть_сверху" ложно).
Нам понадобится такая процедура: void вверх_до_упора_и_обработать
| //{дано: (ОЛ), надо: (ОЛН)}
{
| //{инвариант: ОЛ}
| while( есть_сверху ){
| | вверх_налево ();
|}
| //{ОЛ, Робот в листе}
| обработать();
| //{ОЛН}
}
Основной алгоритм:
//дано: Робот в корне, листья не обработаны
//надо: Робот в корне, листья обработаны
//{ОЛ}
вверх_до_упора_и_обработать ();
//{инвариант: ОЛН}
While(есть_снизу){
| if(есть_справа){
| |//{ОЛН, есть справа}
| | вправо();
| | //{ОЛ}
| | вверх_до_упора_и_обработать();
| }else{
| | //{ОЛН, не есть_справа, есть_снизу}
| | вниз();
|}
}
//{ОЛН, Робот в корне => все листья обработаны}
Вычислительная схема
(1)
(2)
(3)
(4)
Осталось воспользоваться следующими свойствами команд Робота (сверху
записаны условия, в которых выполняется команда, снизу - утверждения о
результате ее выполнения):
{ОЛ, не есть_сверху}
обработать
{ОЛН}
{ОЛ}
вверх_налево
{ОЛ}
{есть_справа, ОЛН}
вправо
{ОЛ}
{не есть_справа, ОЛН}
вниз
{ОЛН}
Пример – задача о ферзях
Рассмотрим классическую задачу о восьми ферзях. Как
известно, в шахматах ферзи атакуют друг друга по
горизонтали, по вертикали или по диагоналям шахматной
доски. Задача состоит в том, чтобы найти такое размещение
8 ферзей на доске 8х8, чтобы никакие два ферзя не
атаковали друг друга. Решение этой задачи легко находится
с помощью перебора с возвратом.
Приведем основной фрагмент реализованного на c++
алгоритма решения этой задачи.
Пример – задача о ферзях
bool place_queens(Queenboard& qb, int col) {
bool inserted = false;
for (int row = 0; row < BOARDSIZE; row++) {
if (! qb.is_space_under_attack(row, col)) {
// ставим ферзя
qb.occupy_space(row, col);
inserted = true;
if (col == BOARDSIZE - 1) {
// найдено решение!
cout << qb << "\n";
return true;
}
else {
// ставим ферзя в следующую колонку
if (place_queens(qb, col + 1)) {
return true;
}
else {
inserted = false;
}
}
}
}
if (! inserted) {
// возвращаемся к предыдущей колонке
qb.clear_column(col - 1);
return false;
}
}
Алгоритм проходит по всему дереву решений (возможных расстановок
ферзей) и, отбрасывая неверные, находит правильные решения.
Заключение
Перебор с возвратом – мощный инструмент для быстрого
нахождения алгоритмов решения разного рода задач,
однако нельзя не отметить, что для многих из них
существуют более эффективные способы решения по
сравнению с приведенным. Однако дидактическая ценность
метода перебора с возвратом в соединении с рекурсией
неоспорима. Во-первых, программы решения многих задач
строятся по единой схеме, а во-вторых, они компактны и
тем самым просты для понимания и усвоения
соответствующих идей.
Download