您的位置:首页 > 其它

回溯法解八皇后问题及再看八皇后问题优化

2015-01-11 09:52 507 查看

1、介绍

先上张图来说明用回溯法八皇后问题的每一步:





2、程序

看着严蔚敏的书写的,写好后运行一次性成功了,程序如下:

//  N皇后问题

#include <iostream>
using namespace std;

#define N 8

bool matrix[N + 1][N + 1] = {0};

bool IsLegal(bool matrix[N + 1][N + 1], const int &i, const int &j)
{
//  判断前面的i-1个棋子与matrix[i][j]是否冲突,i为1时合法

for (int m = 1; m <= i - 1; ++m) {
for (int n = 1; n <= N; ++n) {   //  实际每一行只有一个棋子
if (matrix[m]
== 1) {
if ( n == j || abs(i - m) == abs(j - n) )   //  key, not bad
return false;
}
}
}
return true;
}

void Print(bool matrix[N + 1][N + 1])
{
static int count = 1;
printf("Case %d:\n", count++);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
matrix[i][j] == 1 ? printf("%c ", 2) : printf(". ");
}
cout << endl;
}
cout << endl;
}

void Trial(const int i)
{
//  进入本函数时,在N*N的棋盘前i-1行已放置了互不攻击的i-1个棋子
//  现从第i行起继续为后续棋子选择合适位置

if (i > N)   //  输出当前的合法布局
Print(matrix);
else
for (int j = 1; j <= N; ++j) {
matrix[i][j] = 1;
if ( IsLegal(matrix, i, j) )
Trial(i + 1);
matrix[i][j] = 0;
}
}

int main(void)
{
Trial(1);

return 0;
}
运行结果:



3、数学问题

关于n皇后的解的个数(8皇后是92个解):

[cpp] view
plaincopyprint?





n a(n)

1 1

2 0

3 0

4 2

5 10

6 4

7 40

8 92

9 352

10 724

11 2680

12 14200

13 73712

14 365596

15 2279184

16 14772512

17 95815104

18 666090624

19 4968057848

20 39029188884

21 314666222712

22 2691008701644

23 24233937684440

24 227514171973736

25 2207893435808352

26 22317699616364044

独立解的问题我就不多提了。目前这个数列还没找到通项公式。有意思的是,高斯算八皇后的解的个数时,他算错了,他的答案是76种,不知道他漏了哪种,呵呵。(不过也是4的倍数)

4、想法

那个Trial递归函数我还没弄明白,对着书抄的,要是自己想,难。还有待研究推广。

把判断是否合法的IsLegal函数优化了,原来的程序是O(N^3),现在是 O(N^2):

//  N皇后问题

#include <iostream>
using namespace std;

#define N 8

int matrix[N + 1][N + 1] = {0};
//  matrix[0][j]为空,matrix[i][0]中放第i行的皇后的列坐标(从1开始记)

bool IsLegal(const int &i, const int &j)
{
//  判断前面的i-1个棋子(坐标是matrix[m]
)与matrix[i][j]是否冲突,i为1时合法
for (int m = 1; m <= i - 1; ++m) {
int n = matrix[m][0];
if (  n == j || abs(i - m) == abs(j - n) )
return false;
}
return true;
}

void Print(void)
{
static int count = 1;
printf("Case %d:\n", count++);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
matrix[i][j] == 1 ? printf("%c ", 2) : printf(". ");
}
cout << endl;
}
cout << endl;
}

void Trial(const int &i)
{
//  进入本函数时,在N*N的棋盘前i-1行已放置了互不攻击的i-1个棋子
//  现从第i行起继续为后续棋子选择合适位置

if (i > N)   //  输出当前的合法布局
Print();
else
for (int j = 1; j <= N; ++j) {
matrix[i][j] = 1;
if ( IsLegal(i, j) ) {
matrix[i][0] = j;
Trial(i + 1);
}
matrix[i][j] = 0;
}
}

int main(void)
{
Trial(1);

return 0;
}

再看八皇后问题

1、问题描述

在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

2、解法一

可以用一个一维数组来表示棋盘,seat[i]=j;表示第i行皇后放在第j列位置上。其中j有八种情况,i依次递增,直到增到8时说明每一行都放了一个皇后,这时我们就可以判断棋盘上的皇后是否冲突,如果不冲突则找到一种情况。当然为了提高效率,在每一行第j列放入一个皇后时可以先判断这次放入是否和前面几行已经存在的皇后冲突,如果冲突则把皇后放在下一列,这就是回溯法中的剪枝,可以用一个剪枝函数来实现。

参考代码:

[cpp] view
plaincopy

/******************************************************************************

1. 八皇后问题要求在一个8*8的棋盘上放上8个皇后,使得每一个皇后既攻击不到另外

七个皇后,也不被另外七个皇后所攻击.按照国际象棋的规则,一个皇后可以攻击与之处在

同一行或同一列或同一斜线上的其他任何棋子.因此,八皇后问题等于要求八个皇后中的任

意两个不能被放在同一行或同一列或同一斜线上。

*******************************************************************************/

#include<stdio.h>

# define QUEEN 8

int seat[QUEEN];//皇后的位置,按行放置

int count;//记录总共有多少种放法

void queen(int n);//放置皇后

int ok(int n);//判断放置皇后后是否与其他的n-1个皇后冲突

void main()

{

count=0;

queen(0);//从第0行开始放

printf("总共有%d种放法\n",count);

}

//放置皇后

void queen(int n)

{

int i;

if(n==QUEEN){

count++;

return ;

}

for(i=0;i<QUEEN;i++){

seat
=i;//放置第n行的皇后,把他放在i列上

if(ok(n)){//如果放置没有问题,放置下一个皇后

queen(n+1);

}

}

//return;

}

// 剪枝函数,判断放置皇后后是否与其他的n-1个皇后冲突

int ok(int n)

{

int i;

for(i=0;i<n;i++){

if((seat[i]-i)==(seat
-n)||(seat[i]+i)==(seat
+n)||(seat[i]==seat
))

return 0;

}

return 1;

}

3、解法二

棋盘可以看出是0和1组成的,0表示没有放置皇后,1表示放置皇后

00000001

00000010

00000100

00001000

00010000

00100000

01000000

10000000

给每一行放置一个皇后,不外乎就这八种情况,其中每一种情况对应一个字节,可以用个数为8的char数组来表示这八种情况,然后对这些情况进行全排列,每一种排列判断有没有对角线上的皇后发生冲突,没有冲突则找到一种情况。这里也可以设计一个剪枝函数来提高效率。给出的代码中并没有设计这个剪枝函数。

参考代码:

[cpp] view
plaincopy

#include<stdio.h>

int count=0;//记录有多少种摆法

//字符数组

char seat[]={1,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};

//获得二进制位1在unsigned char中的第几位

int get(unsigned char ch)

{

int i;

for(i=0;i<8;i++)

if(1<<i==ch)

return i;

}

//判断放置皇后后是否与其他的n-1个皇后冲突

int ok()

{

int i,j;

for(i=0;i<8;i++){

for(j=i+1;j<8;j++){//当行列之和或之差相等时说明这两个位置是对角线关系

if((get(seat[i])-i)==(get(seat[j])-j)||(get(seat[i])+i)==(get(seat[j])+j))

return 0;

}

}

return 1;

}

//序数法(排出的序列为有序的)

//str:待排序的字符序列 n:首字符下标 len:字符串长度

void ordinal(unsigned char * str,int n,int len)

{

int i;

unsigned char temp;

if(n==len){

if(ok())

count++;

return;

}

for(i=n;i<len;i++){

temp=str[i];

str[i]=str
;

str
=temp;

ordinal(str,n+1,len);

temp=str[i];

str[i]=str
;

str
=temp;

}

}

void main()

{

ordinal(seat,0,8);

printf("%d\n",count);

}

4、运行结果

总共有92种放法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: