
Переборные задачи
На практике неоднократно встречаются задачи, где требуется обработать (перебрать) все элементы некоторого конечного множества. В простейших случаях элемент множества можно представить числом. Пример такой задачи — поиск заданного числа в одномерном массиве. Здесь для указания элемента массива достаточно задать его индекс. Более сложные случаи, когда элементы интересующего нас множества сами имеют некоторую структуру. (Именно такие задачи обычно относят к переборным.) Во многих случаях элементы множества естественно записывать в виде последовательности чисел фиксированного или переменного размера. Очень многие переборные задачи можно свести к перебору последовательностей. В некоторых задачах (см. например, задачу о коммивояжере в главе 1) требуется генерировать последовательности путем всевозможных перестановок компонент некоторой заданной последовательности фиксированной длины. Правила генерации последовательностей могут меняться в зависимости от задачи: случайным образом, в лексикографическом порядке, путем различных сочетаний компонент и т. п. Примером такой задачи (в действительности она эквивалентна задаче перебора последовательностей) является задача прохождения (обхода) деревьев. Дерево — это граф без циклов, в котором выделена одна вершина, называемая корнем. Для каждой вершины дерева существует единственный путь в нее из корня. Задача состоит в том, чтобы «обойти» все вершины дерева. Если перенумеровать все ребра, выходящие из каждой вершины, то путь из корня, однозначно сопоставляемый каждой вершине, можно будет в свою очередь задать с помощью последовательности номеров проходимых ребер. Лексикографический порядок перебора последовательностей отвечает способу прохождения дерева, известному как перебор в глубину. Находясь в какой-либо вершине, мы сначала пытаемся пойти «в глубину», т. е. удалиться от корня; если это невозможно, то мы возвращаемся назад и пытаемся пойти в глубину по следующему ребру.
Многие практические задачи сводятся к отысканию минимума (максимума) некоторой функции на множестве. Искомым элементом множества часто является функция от времени (например, ищется закон управления самолетом, обеспечивающий наибыстрейшее достижение заданной высоты и скорости). Эту функцию аппроксимируют последовательностью ее значений в дискретные моменты времени, получая задачу поиска минимума в множестве последовательностей. Можно осуществить поиск минимума путем полного перебора всех допустимых последовательностей. Метод динамического программирования позволяет в ряде случаев сократить перебор. Рассмотрим несколько типичных примеров генерации последовательностей и работы с ними при решении переборных задач.
7.1. Полный перебор.
Для решения переборных задач обычно используются разновидности следующей схемы программы:
Схема 1
Var x :элемент множества;
x := первый элемент;
обработать x; While x<> последний элемент Do Begin x: =следующий за x;
обработать x;
End;
Иногда удается добавить к множеству еще один, фиктивный элемент, не подлежащий обработке, который располагается перед первым обрабатываемым элементом, либо вслед за последним В этом случае обработку первого элемента можно выполнить наряду со всеми в цикле:
Схема 2 Схема 3
x:=фиктивный элемент; x:=первый элемент;
Repeat Repeat
x:=следующий за x; обработать x;
обработать x; x:=следующий за x;
Until x = последний элемент; Until x = фиктивный элемент;
Для конкретизации этих схем необходимо ответить на два вопроса.
1.Как будут записываться в программе элементы множества (тип элемент множества)?
2. В каком порядке мы будем перебирать их (инструкция x:=следующий за x;)?
Рассмотрим пример. Подсчитать число счастливых билетов, т. е. таких наборов из шести цифр А,B,C,D,E,F, что A+B+C=D+E+F. Будем решать задачу путем перебора всех комбинаций из шести цифр (это решение далеко не самое эффективное). Таким образом, элемент множества задается последовательно из шести цифр, каждая из которых принимает значения от 0 до 9. Для хранения этой последовательности используем шесть отдельных переменных. В данном случае можно, не следуя буквально введенным выше схемам, организовать перебор с помощью шести вложенных циклов FOR:
Program Happy_Ticket ;
Var A,B,C,D,E,F,k :Integer; { k – число найденных счастливых билетов}
Begin
k:=0;
For A:=0 To 9 Do
For B:=0 To 9 Do
For C:=0 To 9 Do
For D:=0 To 9 Do
For E:=0 To 9 Do
For F:=0 To 9 Do
If A+B+C = D+E+F Then k:=k+1;
Writeln(‘ЧИСЛО СЧАСТЛИВЫХ БИЛЕТОВ=’,k);
End.
Кстати сказать, счастливых билетов 55252 (если считать и 000000), что составляет примерно 1/18 общего числа билетов.