代码改变世界

8皇后问题

2010-12-16 00:36  Rollen Holt  阅读(1271)  评论(0编辑  收藏  举报

八皇后问题是一个古老而著名的问题,它是回溯算法的典型例题。该问题是十九世纪德国著名数学家高斯于1850年提出的:在8行8列的国际象棋棋盘上摆放着 八个皇后。若两个皇后位于同一行、同一列或同一对角线上,则称为它们为互相攻击。在国际象棋中皇后是最强大的棋子,因为它的攻击范围最大,图6-15显示 了一个皇后的攻击范围。

 
图6-15 皇后的攻击范围
现在要求使这八个皇后不能相互攻击,即任意两个皇后都不能处于同一行、同一列或同一对角线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的 象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。现代教学中,把八皇后问题当成一个经典递归算法例题。图6-16显示了 两种八个皇后不相互攻击的情况。

图6-16八个皇后不相互攻击的情况
现在来看如何使用回溯法解决八皇后问题。这个算法将在棋盘上一列一列地摆放皇后直到八个皇后在不相互攻击的情况下都被摆放在棋盘上,算法便终止。当一个新 加入的皇后因为与已经存在的皇后之间相互攻击而不能被摆在棋盘上时,算法便发生回溯。一旦发生这种情况,就试图把最后放在棋盘上的皇后移动到其他地方。这 样做是为了让新加入的皇后能够在不与其它皇后相互攻击的情况下被摆放在棋盘的适当位置上。例如图6-17所示的情况,尽管第7个皇后不会与已经放在棋盘上 的任何一皇后放生攻击,但仍然需要将它移除并发生回溯,因为无法为第8个皇后在棋盘上找到合适的位置。

图6-17 需要发生回溯的情况
算法的回溯部分将尝试移动第7个皇后到第7列的另外一点来为第8个皇后在第8列寻找一个合适的位置。如果第7个皇后由于在第7列找不到合适的位置而无法被移动,那么算法就必须去掉它然后回溯到第6列的皇后。最终算法不断重复着摆放皇后和回溯的过程直到找到问题的解为止。
下面给出了求解八皇后问题的示例程序。
#include <conio.h>
#include <iostream>

using namespace std;
// 首先 要求皇后不冲突,那么每行只应该有一个皇后
// 用queens[]数组在存储每个皇后的位置
// 例如: queens[m] = n 表示 第m行的皇后放在第n列上

#define MAX 8

int sum = 0;
class QueenPuzzle
{
 int queens[MAX]; // 存储每行皇后的列标
 
public:
 void printOut(); // 打印结果
 int IsValid(int n); //判断第n个皇后放上去之后,是否合法
 void placeQueen(int i); // 递归算法 放置皇后
};

void QueenPuzzle::printOut()
{
 for(int i=0; i<MAX; i++)
 {
  for(int j=0; j<MAX; j++)
  {
   if(j == queens[i])
    cout << "Q ";
   else
    cout << "0 ";
  }
  cout << endl;
 }
 cout << endl << "按q键盘退出,按其他键继续" << endl << endl;
 
 if(getch() == 'q')
  exit(0);
}

// 在第i行放置皇后
void QueenPuzzle::placeQueen(int i)
{
 for(int j=0; j<MAX; j++)
 {
  // 如果全部放完了 输出结果
  if(i == MAX)
  {
   sum ++;
   cout << "第" << sum << "组解:" << endl;
   printOut();
   
   return;
  }

  // 放置皇后
  queens[i] = j;

  // 此位置不能放皇后 继续试验下一位置
  if(IsValid(i))
   placeQueen(i+1);
 }
}

//判断第n个皇后放上去之后,是否合法,即是否无冲突 
int QueenPuzzle::IsValid(int n) 

 //将第n个皇后的位置依次于前面n-1个皇后的位置比较。 
 for(int i = 0 ; i < n ; i++) 
 { 
  //两个皇后在同一列上,返回0 
  if(queens[i] == queens[n]) 
   return 0;

  //两个皇后在同一对角线上,返回0 
  if(abs(queens[i] - queens[n]) == (n - i)) 
   return 0; 
 }

 //没有冲突,返回1。
 return 1; 
}

void main()
{
 QueenPuzzle queen;
 queen.placeQueen(0);
 cout << "共" << sum << "组解" << endl;
}


由于回溯 法也是在试图搜索整个解空间中的所有可能的选择,于是有人会误认为回溯法与穷举法差不多,但事实上回溯法要较穷举法在效率上高出很多。这里就以一种简单的 估算方法来对八皇后问题进行一下分析。首先采用穷举法,那么可以容易得出该问题的解空间的结点总数为109601。然后在使用回溯法的前提下随机选取20 条路径,分别估计它们的结点个数并求得总数的平均值。因为已知八皇后问题共有92种解,所以选择20种随机路径进行估计所得出的结果已经可以较为接近实际 数值。经过计算得出回溯法产生的结点数的平均值约为1740,这相对于109601,不足2%,可见回溯法作为一种跳跃性和系统性相结合的搜索方法是具有较高效率的