八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
首先如何决定下一个皇后能不能放这里可以有两种思路,第一种是尝试维护一个8*8的二维矩阵,每次找到一个空位放下一个皇后就把对应行列对角线上的棋格做个标记,如果某行找不到可放皇后的格子就把上一个皇后拿走并把对应行列对角线的标记取消掉;第二种方法直接放弃构造矩阵来假装棋盘,我们把问题更加抽象化,八个皇后能放下一定是一行放一个,我们只需一个数组记录每个皇后的列数(默认第N个放第N行),那么问题就被抽象成了数组的第N个数和前N-1个数不存在几个和差关系即可(比如差不为零代表不在同一列)。
接着想想问题中存在着大量的循环怎么解决比较高效,我们知道递归和迭代一定程度上是可以很容易做到互相转化实现同样的思路的。递归是重复调用函数自身实现循环,迭代是函数内某段代码实现循环,使用递归的话我们应该要有一个能在第N行找到某一列的格子可以放皇后的函数,能找到把参数+1去调用自己去找下一行皇后能放的格子,找不到就算了。如果想用迭代,前面我们说过递归迭代是可以转化的,这种在函数最后调用自己的递归更是极易转化,我们按着迭代的套路在for循环的里按照刚刚递归的思路加几个判断判别循环是continue、break还是返回前一层循环即可。最后还有一种思路,准确来说还是和递归脱离不了关系,学习递归的时候我们我们知道,递归可以看做底层帮你维护的一个堆栈不断地push、pop,知道它的本质我们也可以通过手动维护一个堆栈来模拟这个递归调用的过程,只要构造两个函数backward(往后回溯)、refresh(向前刷新)来模拟堆栈进出即可。
最后我们来分析四个方法(矩阵维护法、递归法、迭代法、手动堆栈法)表现和改进,很明显在代码量上递归会是最短的,而需要运行的空间来看手动堆栈也会比较必要更大的运行内存(如果用VS运行手动堆栈的代码,很有可能会提示你stack溢出,那么你需要修改一下VS的配置给你的程序分配更大的内存)。八皇后问题有很多小细节可以改进(具体实现大家自己来,为了方便我就说一些我想到的点):很明显棋盘是对称的,如果你得出了一个解法那么一定有行对称列对称对角线对称的另外三种对称的摆法,这样就可以减少一些计算量。
(一)递归法
#include <iostream> using namespace std; int queen[9]={-1,-1,-1,-1,-1,-1,-1,-1,-1}; int cou=0; bool available(int pointi,int pointj) //判断某个皇后是否与已有皇后冲突 { for(int i=1;i<pointi;i++) { if(pointj==queen[i]) return false; //同一列 if((pointi-i)==(pointj-queen[i]))return false; //同一主对角线 if((pointi-i)+(pointj-queen[i])==0)return false; //同一副对角线 } return true; } void findSpace(int queenNumber) //在第queenNumber行找能放皇后的位置 { for(int i=1;i<9;i++) //从1~8遍历这一行的八个空位 { if(available(queenNumber,i)) //如果可以放这个位置就记录下第queenNumber个皇后的位置 { queen[queenNumber]=i; if(queenNumber==8) //如果8个皇后都放满了统计一下 { cou++; return; } int nextNumber=queenNumber+1; //还有皇后没放递归放下一个皇后 findSpace(nextNumber); } } queen[--queenNumber]=-1; //如果这一行没有可放的位置说明上一行皇后放的位置不行,要为上一个皇后找新的位置 return; } int main() { findSpace(1); //从(1,1)开始递归 printf("%d",cou); return 0; }