| Россия |
Обход дерева. Перебор с возвратами
3.1.7.Доказать, что число операций в этой программе по порядку равно числу вершин дерева. (Как и в других программах, которые отличаются от этой лишь пропуском некоторых команд обработать.)
Указание. Примерно каждое второе действие при исполнении этой программы - обработка вершины, а каждая вершина обрабатывается максимум дважды.
Вернемся теперь к нашей задаче о ферзях (где из всех
программ обработки дерева понадобится лишь первая,
самая простая).
Реализуем операции с деревом позиций. Позицию будем
представлять с помощью переменной k: 0..n (число ферзей)
и массива c: array[1..n] of 1..n ( c[i] -
координаты ферзя на i -ой горизонтали; при
значение c[i] роли не играет).
Предполагается, что все позиции допустимы (если убрать
верхнего ферзя, остальные не бьют друг друга).
program queens;
| const n = ...;
| var
| k: 0..n;
| c: array [1..n] of 1..n;
|
| procedure begin_work; {начать работу}
| begin
| | k := 0;
| end;
|
| function danger: boolean; {верхний ферзь под боем}
| | var b: boolean; i: integer;
| begin
| | if k <= 1 then begin
| | | danger := false;
| | end else begin
| | | b := false;
| | | i := 1;
| | | {b <=> верхний ферзь под боем ферзей с номерами < i}
| | | while i <> k do begin
| | | | b := b or (c[i]=c[k]) {вертикаль}
| | | | or (abs(c[i]-c[k]))=abs(i-k)); {диагональ}
| | | | i := i+1;
| | | end;
| | | danger := b;
| | end;
| end;
|
| function is_up: boolean; {есть_сверху}
| begin
| | is_up := (k < n) and not danger;
| end;
|
| function is_right: boolean; {есть_справа}
| begin
| | is_right := (k > 0) and (c[k] < n);
| end;
| {возможна ошибка: при k=0 не определено c[k]}
|
| function is_down: boolean; {есть_снизу}
| begin
| | is_down := (k > 0);
| end;
|
| procedure up; {вверх_налево}
| begin {k < n, not danger}
| | k := k + 1;
| | c [k] := 1;
| end;
|
| procedure right; {вправо}
| begin {k > 0, c[k] < n}
| | c [k] := c [k] + 1;
| end;
|
|
| procedure down; {вниз}
| begin {k > 0}
| | k := k - 1;
| end;
|
| procedure work; {обработать}
| | var i: integer;
| begin
| | if (k = n) and not danger then begin
| | | for i := 1 to n do begin
| | | | write ('<', i, ',' , c[i], '> ');
| | | end;
| | | writeln;
| | end;
| end;
|
|
| procedure UW; {вверх_до_упора_и_обработать}
| begin
| | while is_up do begin
| | | up;
| | end
| | work;
| end;
|
begin
| begin_work;
| UW;
| while is_down do begin
| | if is_right then begin
| | | right;
| | | UW;
| | end else begin
| | | down;
| | end;
| end;
end.3.1.8.Приведенная программа тратит довольно много времени на выполнение проверки есть_сверху (проверка, находится ли верхний ферзь под боем, требует числа действий порядка n ). Изменить реализацию операций с деревом позиций так, чтобы все три проверки есть_сверху/справа/снизу и соответствующие команды требовали бы количества действий, ограниченного не зависящей от n константой.
Решение. Для каждой вертикали, каждой восходящей и каждой нисходящей диагонали будем хранить булевское значение - сведения о том, находится ли на этой линии ферзь (верхний ферзь не учитывается). (Заметим, что в силу допустимости позиции на каждой из линий может быть не более одного ферзя.)
3.2. Обход дерева в других задачах
3.2.1.Использовать метод обхода дерева для решения следующей задачи: дан массив из n целых положительных чисел
и число s ; требуется узнать,
может ли число s быть представлено как сумма некоторых
из чисел массива a. (Каждое число можно использовать не
более чем по одному разу.)
Решение. Будем задавать k -позицию
последовательностью из k булевских значений,
определяющих, входят ли в сумму числа
или не входят. Позиция допустима, если ее сумма не превосходит s.
Замечание. По сравнению с полным перебором всех
подмножеств тут есть некоторый выигрыш. Можно
также предварительно отсортировать массив a в убывающем
порядке, а также считать недопустимыми те позиции,
в которых сумма отброшенных членов больше, чем разность
суммы всех членов и s. Последний прием называют "методом ветвей и границ". Но принципиального улучшения по
сравнению с полным перебором тут не получается (эта задача,
как говорят,
-полна, подробности см. в книге Ахо,
Хопкрофта и Ульмана "Построение и анализ вычислительных алгоритмов", Мир, 1979, а также в книге Гэри и Джонсона "Вычислительные машины и труднорешаемые задачи", Мир,
1982). Традиционное название этой задачи - "задача
о рюкзаке" (рюкзак общей грузоподъемностью s нужно
упаковать под завязку, располагая предметами веса
). См. также в
"Как обойтись без рекурсии"
(Как обойтись без рекурсии) алгоритм ее решения, полиномиальный по
(использующий "динамическое программирование").
3.2.2.
Перечислить все последовательности из
нулей, единиц и двоек, в которых никакая группа цифр не повторяется два раза подряд (нет куска вида
).
3.2.3.
Аналогичная задача для последовательностей нулей и единиц,
в которых никакая группа цифр не повторяется три раза
подряд (нет куска вида
).
К этой же категории относятся задачи типа "можно ли сложить данную фигуру из пентамино" и им подобные. В них важно умелое сокращение перебора (вовремя распознать, что имеющееся расположение фигурок уже противоречит требованиям, и по этой ветви поиск не продолжать).