Нахождение всех комбинаций расстановки n ферзей на доске n X n

Дата: 15.05.2014

		

Государственный комитет Российской Федерации
по высшему и среднеспециальному образованию

Красноярский Государственный Технический Университет

Курсовая работа

по курсу

Математическая логика и теория алгоритмов

Выполнил студент гр. ВТ27-4

Попов А.В.

Проверила:
Пестунова Т.М.

1998

Содержание.

1. Постановка задачи (стр.3).
2. Построение модели (стр.3).
3. Описание алгоритма (стр.4).
4. Доказательство правильности алгоритма (стр.7).
5. Блок-схема алгоритма (стр.8).
6. Описание переменных и программа (стр.9).
7. Расчёт вычислительной сложности (стр.11).
8. Тестирование (стр.11).
9. Список литературы (стр.12).

Постановка задачи.
Перечислить все способы расстановки n ферзей на шахматной доске n на n,
при которых они не бьют друг друга.

Построение модели.
Очевидно, на каждой из n горизонталей должно стоять по ферзю. Будем
называть k-позицией (для k = 0, 1,…,n) произвольную расстановку k ферзей
на k нижних горизонталях (ферзи могут бить друг друга). Нарисуем «дерево
позиций»: его корнем будет единственная 0-позиция, а из каждой k-позиции
выходит n стрелок вверх в (k+1)-позиции. Эти n позиций отличаются
положением ферзя на (k+1)-ой горизонтали. Будем считать, что расположение
их на рисунке соответствует положению этого ферзя: левее та позиция, в
которой ферзь расположен левее.

Дерево позиций для n = 2

Данное дерево представлено только для наглядности и простоты
представления для n=2.
Среди позиций этого дерева нам надо отобрать те n-позиции, в которых
ферзи не бьют друг друга. Программа будет «обходить дерево» и искать их.
Чтобы не делать лишней работы, заметим вот что: если в какой-то k-позиции
ферзи бьют друг друга, то ставить дальнейших ферзей смысла нет. Поэтому,
обнаружив это, мы будем прекращать построение дерева в этом направлении.
Точнее, назовем k-позицию допустимой, если после удаления верхнего
ферзя оставшиеся не бьют друг друга. Наша программа будет рассматривать
только допустимые позиции.

Описание алгоритма.
Разобьем задачу на две части: (1) обход произвольного дерева и (2)
реализацию дерева допустимых позиций.
Сформулируем задачу обхода произвольного дерева. Будем считать, что у
нас имеется Робот, который в каждый момент находится в одной из вершин
дерева. Он умеет выполнять команды:

вверх_налево (идти по самой левой из выходящих вверх стрелок)
вправо (перейти в соседнюю справа вершину)
вниз (спуститься вниз на один уровень)

вверх_налево
вправо
вниз

и проверки, соответствующие возможности выполнить каждую из команд,
называемые «есть_сверху», «есть_справа», «есть_снизу» (последняя истинна
всюду, кроме корня). Обратите внимание, что команда «вправо» позволяет
перейти лишь к «родному брату», но не к «двоюродному».
Будем считать, что у Робота есть команда «обработать» и что его задача
— обработать все листья (вершины, из которых нет стрелок вверх, то есть где
условие «есть_сверху» ложно). Для нашей шахматной задачи команде
обработать будет соответствовать проверка и печать позиции ферзей.
Доказательство правильности приводимой далее программы использует такие
определения. Пусть фиксировано положение Робота в одной из вершин дерева.
Тогда все листья дерева разбиваются на три категории: над Роботом, левее
Робота и правее Робота. (Путь из корня в лист может проходить через вершину
с Роботом, сворачивать влево, не доходя до нее и сворачивать вправо, не
доходя до нее.) Через (ОЛ) обозначим условие «обработаны все листья левее
Робота», а через (ОЛН) — условие «обработаны все листья левее и над
Роботом».

Нам понадобится такая процедура:

procedure вверх_до_упора_и_обработать
{дано: (ОЛ), надо: (ОЛН)}
begin
{инвариант: ОЛ}
while есть_сверху do begin
вверх_налево
end
{ОЛ, Робот в листе}
обработать;
{ОЛН}
end;

Основной алгоритм:

дано: Робот в корне, листья не обработаны
надо: Робот в корне, листья обработаны

{ОЛ}
вверх_до_упора_и_обработать
{инвариант: ОЛН}
while есть_снизу do begin
if есть_справа then begin {ОЛН, есть справа}
вправо;
{ОЛ}
вверх_до_упора_и_обработать;
end else begin
{ОЛН, не есть_справа, есть_снизу}
вниз;
end;
end;
{ОЛН, Робот в корне => все листья обработаны}

Осталось воспользоваться следующими свойствами команд Робота (сверху
записаны условия, в которых выполняется команда, снизу — утверждения о
результате ее выполнения):

1) {ОЛ, не есть_сверху} обработать {ОЛН}
2) {ОЛ} вверх_налево {ОЛ}
3) {есть_справа, ОЛН} вправо {ОЛ}
4) {не есть_справа, ОЛН} вниз{ОЛН}

Теперь решим задачу об обходе дерева, если мы хотим, чтобы
обрабатывались все вершины (не только листья).
Решение. Пусть x — некоторая вершина. Тогда любая вершина y относится
к одной из четырех категорий. Рассмотрим путь из корня в y. Он может:
а) быть частью пути из корня в x (y ниже x);
б) свернуть налево с пути в x (y левее x);
в) пройти через x (y над x);
г) свернуть направо с пути в x (y правее x);
В частности, сама вершина x относится к категории в). Условия теперь
будут такими:
(ОНЛ) обработаны все вершины ниже и левее;
(ОНЛН) обработаны все вершины ниже, левее и над.
Вот как будет выглядеть программа:

procedure вверх_до_упора_и_обработать
{дано: (ОНЛ), надо: (ОНЛН)}
begin
{инвариант: ОНЛ}
while есть_сверху do begin
обработать
вверх_налево
end
{ОНЛ, Робот в листе}
обработать;
{ОНЛН}
end;

Основной алгоритм:

дано: Робот в корне, ничего не обработано
надо: Робот в корне, все вершины обработаны

{ОНЛ}
вверх_до_упора_и_обработать
{инвариант: ОНЛН}
while есть_снизу do begin
if есть_справа then begin {ОНЛН, есть справа}
вправо;
{ОНЛ}
вверх_до_упора_и_обработать;
end else begin
{ОЛН, не есть_справа, есть_снизу}
вниз;
end;
end;
{ОНЛН, Робот в корне => все вершины обработаны}

Приведенная только что программа обрабатывает вершину до того, как
обработан любой из ее потомков. Теперь изменим ее, чтобы каждая вершина, не
являющаяся листом, обрабатывалась дважды: один раз до, а другой раз после
всех своих потомков. Листья по-прежнему обрабатываются по разу:
Под «обработано ниже и левее» будем понимать «ниже обработано по разу,
слева обработано полностью (листья по разу, остальные по два)». Под
«обработано ниже, левее и над» будем понимать «ниже обработано по разу,
левее и над — полностью».

Программа будет такой:

procedure вверх_до_упора_и_обработать
{дано: (ОНЛ), надо: (ОНЛН)}
begin
{инвариант: ОНЛ}
while есть_сверху do begin
обработать
вверх_налево
end
{ОНЛ, Робот в листе}
обработать;
{ОНЛН}
end;

Основной алгоритм:

дано: Робот в корне, ничего не обработано
надо: Робот в корне, все вершины обработаны

{ОНЛ}
вверх_до_упора_и_обработать
{инвариант: ОНЛН}
while есть_снизу do begin
if есть_справа then begin {ОНЛН, есть справа}
вправо;
{ОНЛ}
вверх_до_упора_и_обработать;
end else begin
{ОЛН, не есть_справа, есть_снизу}
вниз;
обработать;
end;
end;
{ОНЛН, Робот в корне => все вершины обработаны полностью}

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

Блок-схема алгоритма.

Описание переменных и программа.
Теперь реализуем операции с деревом позиций. Позицию будем
представлять с помощью переменной k: 0..n (число ферзей) и массива c:
array [1..n] of 1..n (c [i] — координаты ферзя на i-ой горизонтали; при i >
k значение 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_up := (k > 0);
end;

procedure up; {вверх_налево}
begin {k < n}
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.

Расчёт вычислительной сложности.
Емкостная сложность:
В программе используется одномерный массив размерности n, поэтому объём
входа и объём выхода совпадают и равны n. Количество пременных равно
3(i,b,k) + 1(const n), т.е. объём промежуточных данных равен 4.
С(n)=n+4
Временная сложность:

Если рассматривать обработку каждого листа, без проверки на пути к
нему, то временная сложность T(n) = n0+n1+n2+n3+…+nn .

Но в случае, когда каждая вершина проверяется, временная сложность T(n) =
o(n0+n1+n2+n3+…+nn). И это тем вернее, чем больше n. Данный вывод получен
на основе приведённых ниже статистических данных:

|1 |2 |3 |4 |5 |6 |7 | |Общее кол-во листьев |2 |7 |40 |341 |3906 |55987
|960800 | |Кол-во вершин построенного дерева. |2 |3 |4 |17 |54 |153 |552 |
|Время построения(сек) |<0.01 |<0.01 |<0.01 |<0.01 |<0.01 |<0.01 |<0.01 | |
|8 |9 |10 |11 |12 |13 | |Общее кол-во листьев |[pic] |[pic] |[pic] |[pic]
|[pic] |[pic] | |Кол-во вершин построенного дерева. |2057 |8394 |35539
|166926 |856189 |4674890 | |Время построения(сек) |<0.01 |0.21 |1.20 |6.48
|37.12 |231.29 | |

Тестирование.
Построенная по описанному алгоритму программа при различных n выдаёт
следующие данные:
n=4
<1,2><2,4><3,1><4,3>
<1,3><2,1><3,4><4,2>
Т.е. количество расстановок равно 2. Ниже приведена таблица зависимости
от n количества решений (R).

n = |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 | |
|R= |1 |0 |0 |2 |10 |4 |40 |92 |352 |724 |2680 |14200 |73712|

Cписок литературы.
1) Кузнецов О.П. Адельсон-Вельский Г.М. Дискретная математика для
инженера. – М.: Энергоатомиздат, 1988.
2) Евстигнеев В.А. Применение теории графов в программировании. –
М.:Наука, 1984.
3) Основной алгоритм находился на BBS “Master of Univercity” в файле
shen.rar в файловой области “Bardak” (тел. 43-27-03; время работы 21.00
– 7.00; FTN адрес – 2:5090/58).

————————
[pic]

[pic]

Скачать реферат

Метки:
Автор: 

Опубликовать комментарий