Все возможные решения алгоритма n-Queen
При реализации алгоритма для всех возможных решений проблемы n-Queen я обнаружил, что одно и то же решение достигается многими ветвями. Есть ли хороший способ генерировать уникальные решения проблемы n-Queens? Как избежать дублирования решений, генерируемых различными ветвями (кроме хранения и сравнения)?
Вот что я попробовал для первого решения: http://www.ideone.com/hDpr3
Код:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* crude */
#define QUEEN 'Q'
#define BLANK '.'
int is_valid (char **board, int n, int a, int b)
{
int i, j;
for (i=0; i<n; i++)
{
if (board[a][i] == QUEEN)
return 0;
if (board[i][b] == QUEEN)
return 0;
}
for (i=a, j=b; (i>=0) && (j>=0); i--, j--)
{
if (board[i][j] == QUEEN)
return 0;
}
for (i=a, j=b; (i<n) && (j<n); i++, j++)
{
if (board[i][j] == QUEEN)
return 0;
}
for (i=a, j=b; (i>=0) && (j<n); i--, j++)
{
if (board[i][j] == QUEEN)
return 0;
}
for (i=a, j=b; (i<n) && (j>=0); i++, j--)
{
if (board[i][j] == QUEEN)
return 0;
}
return 1;
}
void show_board (char **board, int n)
{
int i, j;
for (i=0; i<n; i++)
{
printf ("\n");
for (j=0; j<n; j++)
{
printf (" %c", board[i][j]);
}
}
}
int nqdfs_all (char **board, int n, int d)
{
int i, j, ret = 0;
/* the last queen was placed on the last depth
* therefore this dfs branch in the recursion
* tree is a solution, return 1
*/
if (d == n)
{
/* Print whenever we find one solution */
printf ("\n");
show_board (board, n);
return 1;
}
/* check all position */
for (i=0; i<n; i++)
{
for (j=0; j<n; j++)
{
if (is_valid (board, n, i, j))
{
board[i][j] = QUEEN;
nqdfs_all (board, n, d + 1);
board[i][j] = BLANK;
}
}
}
return ret;
}
int nqdfs_first (char **board, int n, int d)
{
int i, j, ret = 0;
/* the last queen was placed on the last depth
* therefore this dfs branch in the recursion
* tree is a solution, return 1
*/
if (d == n)
return 1;
/* check all position */
for (i=0; i<n; i++)
{
for (j=0; j<n; j++)
{
if (is_valid (board, n, i, j))
{
board[i][j] = QUEEN;
ret = nqdfs_first (board, n, d + 1);
if (ret)
{
/* if the first branch is found, tell about its
* success to its parent, we will not look in other
* solutions in this function.
*/
return ret;
}
else
{
/* this was not a successful path, so replace the
* queen with a blank, and prepare board for next
* pass
*/
board[i][j] = BLANK;
}
}
}
}
return ret;
}
int main (void)
{
char **board;
int n, i, j, ret;
printf ("\nEnter \"n\": ");
scanf ("%d", &n);
board = malloc (sizeof (char *) * n);
for (i=0; i<n; i++)
{
board[i] = malloc (sizeof (char) * n);
memset (board[i], BLANK, n * sizeof (char));
}
nqdfs_first (board, n, 0);
show_board (board, n);
printf ("\n");
return 0;
}
Для генерации всех возможных решений я использовал один и тот же код nqdfs_all ()
функция, но не вернул элемент управления родителю, вместо этого продолжил перечисление и проверку. Вызов этой функции отображает повторяющиеся результаты, достигнутые различными ветвями.
1 ответ
Вы должны использовать тот факт, что каждая королева должна быть размещена в отдельном столбце. Если вы уже поместили k ферзей в первые k столбцов, рекурсивно поместите номер ферзя k+1 в столбец k+1 и пройдите через строки с 1 по n (а не через все n*n ячеек, как вы это делаете в настоящее время). Продолжайте с k:=k+1 для каждого действительного размещения. Это позволит избежать дублирования результатов, поскольку этот алгоритм вообще не создает дублирующихся плат.
РЕДАКТИРОВАТЬ: к вашему вопросу об избежании симметрии: часть из них можно избежать заранее, например, ограничив королеву 1 в столбце 1 в строки 1,...n/2
, Если вы хотите полностью избежать вывода симметричных решений, вам придется хранить каждое найденное решение в списке, и всякий раз, когда вы находите новое решение, перед его распечатыванием проверяйте, присутствует ли один из его симметричных эквивалентов в списке.
Чтобы сделать это более эффективным, вы можете работать с "каноническим представлением" каждой доски, определяемым следующим образом. Создайте все симметричные платы данного, упакуйте каждую из них в байтовый массив, и среди этих массивов сохраните массив, который, интерпретируемый как большое число, имеет минимальное значение. Это упакованное представление является уникальным идентификатором класса симметрии каждой платы и может быть легко помещено в словарь / хэш-таблицу, что делает проверку того, что этот класс симметрии уже оказался очень эффективным.