МОГУТ ЛИ ВОСЕМЬ ФЕРЗЕЙ НЕ БИТЬ ДРУГ ДРУГА?




 

Исходным пунктом обсуждения будет такая головоломка: могут ли восемь ферзей (на шахматной доске 8*8) не бить друг друга? Более точно – требуется найти все расстановки восьми ферзей, в которых никакие два ферзя не стоят на одной вертикали,горизонтали или диагонали(если такие позиции вообще существуют).

Прежде чем обсуждать вопрос о написании программы, решающей эту задачу, расскажем об истории ее решения без машины. Задача поставлена в 1848 году немецким шахматистом М.Беццелем. В июне 1850г. Ф.Наук опубликовал 60 решений. Великому математику К.Гауссу удалось найти 72 решения. Однако вскоре его результат перекрыл тот же Наук,нашедший 92 решения. В 1874г. английский математик Д.Глешер доказал, что больше решений не существует.

Вернемся к нашей головоломке. Легко заметить, что на каждой вертикали должен стоять один ферзь, поскольку вертикалей столько же сколько ферзей,а никакие два ферзя не должны стоять на одной вертикали. Аналогичным образом на каждой горизонтали должен стоять один ферзь. Если бы у нас были не ферзи, а ладьи, то эти условия были бы достаточными, чтобы ладьи не били друг друга. Знакомые с комбинаторикой легко сообразят, что в случае с ладьей существует 8!=1*2*3*…*8 решений.

В нашем случае (когда расставляют не ладей, а ферзей) существенны и диагонали, так что задача, видимо, сложнее. Попытаемся перебрать все варианты и найти среди них искомые. Приняв такое решение, сталкиваемся со следующими вопросами: нам нужно быть уверенными, что в ходе перебора мы не пропустим ни одного решения головоломки. Кроме того, было бы желательно не рассматривать одну и ту же позицию многократно, чтобы по возможности избежать лишней работы. Таким образом, нам нужно составить алгоритм (план) перебора, позволяющий достигнуть этих целей. Чесно говоря это и смоставляет основную тему данной главы, азадача о восьми ферзях является лишь поводом. Поэтому, составив интересующий нас алгоритм, мы не будем пытаться его выполнить ни вручную, ни с помощью компьютера.

Итак, мы хотим перебрать и испробовать все варианты, чтобы быть уверенными, что ни одного решения головоломки не пропустили. Однако выражение “все варианты” не ясно: хотим ли мы рассматривать все расстановки ферзей на доске? или все расстановки восьми ферзей? Или все расстановки, где на каждой горизонтали стоит по ферзю? или еще что-нибудь?

Нужно, таким образом, определить, какие конфигурации будут рассматриваться в качестве “кандидатов” в решении головоломки. Здесь есть две опасности. Первая состоит в том, что кандидатов окажется много. А ведь чем их больше, тем более трудоемким окажется перебор. Этой опасности можно было бы избежать, считая кандидатами расстановки восемь ферзей, в которых никакие два ферзя не бьют друг друга. Тогда количество кандидатов, разумеется, минимально, но не ясно, как их перебирать: уметь их перебирать значит уметь решать исходную головоломку.

Мы будем представлять себе позицию на доске как результат появления на доске ферзей друг за другом. Поскольку порядок появления ферзей безразличен, и на каждой горизонтали должно стоять по ферзю, можно представлять себе, что ферзей ставят снизу вверх – сначала ставится ферзь на первую горизонталь, затем на вторую и т.д. Может оказаться, что ферзь, поставленный последним, попадает под бой уже стоящих на доске. В этом случае наша позиция бесперспективна и ставить ферзей дальше смысла нет.

Возникает “дерево вариантов”. Внизу, в его корне, находится пустая позиция – позиция, в которой нет ни одного ферзя. Выше нее нарисованы все позиции, которые могут получиться, когда мы поставим ферзя на первую горизонталь. И с каждой такой позиции можно получить восемь позиций, поставив следующего ферзя на одну из клеток второй горизонтали.

Дерево вариантов строится следующим образом. Внизу рисуем пустую позицию. Далее применяется такое правило: над каждой уже имеющейся в дереве позицией, в которой ферзи не бьют друг друга и есть свободная горизонталь, рисуем восемь других, соответствующих восьми возможным положениям ферзя на нижней из свободных горизонталей, и соединяем их с ней линиями. Таким образом, “ листьями” нашего дерева будут, во-первых, бесперспективные позиции и, во-вторых, решения нашей головоломки, т.е. позиции, в которых все горизонтали заполнены ферзями и они друг друга не бьют.

ЗАДАЧА О ВОСЬМИ ФЕРЗЯХ

 

Задача о восьми ферзях — хорошо известный пример ис­пользования метода проб и ошибок и алгоритмов с возвратом. В 1850 г. ею занимался К. Ф. Гаусс, но полного ее ре­шения он не дал. Это и не удивительно. Для подобных задач характерно отсутствие аналитического решения. Вместо этого они требуют большого объема изнурительных вычислений, терпения и аккуратности. Поэтому такие задачи стали почти исключительно прерогативой вычислительных машин, кото­рые обладают этими свойствами в гораздо большей степени, чем люди, даже гениальные.

Задача о восьми ферзях поставлена следующим образом: нужно так расставить восемь ферзей на шахматной доске, чтобы ни один ферзь не угрожал дру­гому.

Используя схему (5.4) в качестве образца, мы легко по­лучаем следующую предварительную версию алгоритма:

 

procedure try(i: integer );

Begin

инициировать выбор позиции для i-го ферзя;

repeat выбрать позицию;

if безопасно then

begin поставить ферзя; (5.5)

if i < 8 then

begin try(i+l);

if неудачно then убрать ферзя

End

End

until удачно or нет больше позиции

End

Чтобы идти дальше, нужно выбрать некоторое представление для данных. Поскольку мы знаем, что по шахматным прави­лам ферзь бьет все фигуры, расположенные на той же гори­зонтали, вертикали или диагонали доски, то мы заключаем, что каждая вертикаль может содержать одного и только од­ного ферзя, так что j -ro ферзя можно сразу помещать на i -ю вертикаль. Итак, параметр i становится индексом вер­тикали, а выбор позиции ограничивается восемью возмож­ными значениями индекса горизонтали j.

Осталось решить, как представить расположение восьми ферзей на доске. Очевидно, что доску можно было бы вновь изобразить в виде квадратной матрицы, но после некоторого размышления мы обнаруживаем, что такое представление значительно усложнило бы проверку безопасности позиции. Это крайне нежелательно, поскольку такая операция выпол­няется наиболее часто. Поэтому нужно выбрать представле­ние, которое насколько возможно упростит эту проверку. Лучше всего сделать наиболее доступной ту информацию, ко­торая действительно важна и чаще всего используется. В на­шем случае это не расположение ферзей, а информация о том, помещен ли ферзь на данной горизонтали или диаго­нали. (Мы уже знаем, что на каждой k -ой вертикали уже по­мещен один ферзь для 1 k i.) Это приводит к следую­щим описаниям переменных:

var х: array [1.. 8] of integer;

а: array [1.. 8] of Boolean;

b: array [b1.. b2] of Boolean; (5.6)

с: array [cl.. c2] of Boolean;

x[i] указывает позицию ферзя на i-й вертикали;

a[j] означает, что на j-й горизонтали нет ферзя;

b [k] означает отсутствие ферзя на k-й > - диагонали;

с [k] означает отсутствие ферзя на k-й - диагонали.

Выбор индексных границ b1, b2, с1, с2 определяется, ис­ходя из способа, которым вычисляются индексы b и с; мы замечаем, что на -диагонали все поля имеют одну и ту же сумму координат i и, а на -диагонали постоянна разность координат i - j. Соответствующее решение показано в про­грамме 5.2.

 

 


Рис. 5.2. Одно из решений задачи о восьми ферзях

 

При таких данных оператор «поставить ферзя » принимает следующую форму:

х [i]:= j; а [ j ]: = false; b [i + j]:= false; с [ij ]:= false

 

При уточнении оператора «убрать ферзя» мы получаем

 

a[j]:=true; b [i + j]:= true; c[i — j]:=true

 

а условие «безопасно» выполняется, если поле <i,j> нахо­дится на горизонтали и диагонали, которые еще свободны (представлены как true), что можно описать логическим вы­ражением

a[j]/\b[i + j]/\c[i-j]

Этим завершается разработка алгоритма, который пол­ностью описан в программе 5.2. Она дает решение x = (1, 5, 8, 6, 3, 7, 2, 4), которое изображено на рис. 5.2.

program eightqueen1;

{поиск одного решения задачи о восьми ферзях}

var i: integer; q: boolean;

a: array [ 1.. 8] of boolean;

b: array [ 2.. 16] of boolean;

c: array [-7.. 7] of boolean;

x: array [ 1.. 8] of integer;

procedure try(i: integer; var q: boolean);

var j: integer;

begin j:= 0;

repeat j:=j+1; q:=false;

if a[j] and b[i+j] and c[i-j] then

begin x[i]:= j;

a[j]:= false; b[i+j]:= false; c[i-j]:= false;

if i< 8 then

begin try(i+ 1,q);

if not q then

begin a[j]:= true; b[i+j]:=true; c[i-j]:= true

End

end else q:=true

End

until q or (j=8)

end {try};

Begin

for i:=1 to 8 do a[i]:= true;

for i:=2 to 16 do b[i]:=true;

for i:= -7 to 7 do c[i]:= true;

try (1,q);

if q then

for i:= 1 to 8 do write (x[i]:4);

Writeln

end.

 

Программа 5.2. Восемь ферзей (одно решение).

 

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

К этому обобщению легко перейти. Вспомним, что множе­ство возможных путей строится по строго определенной системе, так чтобы никакой путь не предлагался более одного раза. Это свойство алгоритма соответствует поиску по де­реву, при котором каждый узел посещается только один раз. Это позволяет — после того как решение найдено и должным образом зафиксировано — просто переходить на следующий возможный путь. Общая схема такого алгоритма (5.7) получена из (5.4):

procedure try(i: integer);

var k: integer;

Begin

for k:= 1 to т do

begin выбор k-го пути;

if приемлемо then (5.7)

begin запись его

if i < n then try(i+l) else печать решения

стирание записи

End

End

End

 

Заметим, что благодаря тому, что условие окончания цикла упростилось до одной составляющей kт, оператор цикла с постусловием естественно заменить на оператор цикла с па­раметром. К удивлению, поиск всех возможных решений опи­сывается более простой программой, чем поиск одного ре­шения.

Обобщенный алгоритм, который находит все 92 решения задачи о восьми ферзях, представлен в программе 5.3. На са­мом деле существует только 12 принципиально различных ре­шений; наша программа не учитывает симметрию.

 

Таблица 5.2. Двенадцать решений задачи о восьми ферзях

X1 X2 X3 X4 X5 X6 X7 X8 N
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 

 

В табл. 1.2 приведены первые 12 решений. Число N указы­вает частоту проверок безопасности полей. Ее среднее значение для всех 92 решений равно 161.

 

program eightqueens;

var i: integer;

a: array [ 1.. 8] of boolean;

b: array [ 2.. 16] of boolean;

c: array [-7.. 7] of boolean;

x: array [ 1.. 8] of integer;

procedure print;

var k: integer; begin for k:= 1 to 8 do write(x[k]: 4);

Writeln

end {print};

procedure try(i: integer);

var j: integer; begin

for j:=1 to 8 do

if a[j] and b[i+j] and c[i-j] then

begin x[i]:=j;

a[j]:= false; b[i+j]:= false; c[i-j]:= false;

if i < 8 then try(i+1) else print;

a[j]:= true; b[i+j]:=true; c[i-j]:=true

End

end {try};

Begin

for i:=1 to 8 do a[i]:=true;

for i:=2 to 16 do b[i]:=true;

for i:= -7 to 7 do c[i]:=true;

try(1)

end.

Программа 5.3. Восемь ферзей (все решения).

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-04-30 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: