八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
问题条件:任两个皇后都不能处于同一条横行、纵行或斜线上。
八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 = 1 或 n1 ≥ 4 时问题有解
解决基本思想:递归
程序基本模块:
- 判断模块conflict():用来判断准备下放的皇后与已经放置在棋盘上的皇后是否有冲突
- 递归模块queens():为准备下放的皇后遍历位置,若有符合位置(与之前皇后没冲突),则将皇后暂放该位置,并进入下一个递归。
- 主函数main():没什么好说的
模块实现:
1、判断模块conflict():
- 假定棋盘坐标系如图:
- 设图上标注的红色块坐标为(x0,y0),可知两条对角线的直线方程可写作:y-y0=+-(x-x0)
- 再设:下一个要放的皇后坐标为(nextx,nexty),则由条件可知,(nextx,nexty)不在上述直线中
- 现在讨论行、列冲突问题:
- 行:假定我们一行一行地放置皇后,则下一行必定比上一行数大,因此不存在行的问题
- 列:很简单:nextx!=x0即可,即为(nextx-x0)!=0
- 代码如下:
int conflict(int* state, int nextx, int nexty) {//判断准备放下的后是否与之前的后有冲突 //state是储存之前皇后的位置数组, //nextx,nexty分别代表准备放下皇后的列和行 //判断的条件:不同列,即(state[i] - nextx)!=0 //不同行:由于nexty必定大于已有的行数,则必定不同行 //不在对角线上,先写出已知(x0,y0)的对角线方程: //y-y0=+-(x-x0); 即(nextx,nexty)不在直线上 //综上,条件如下: for (int i = 0; i < nexty; i++){ if ((abs(state[i] - nextx) == (nexty - i)) || \ (abs(state[i] - nextx) == 0)) return 1; } return 0; }
2、递归模块 queens():
- 参数说明:num表示总共的皇后数,len表示已经放下的皇后数,state数组是储存已经放下的皇后位置
- 先从最后一个皇后说起:准备下方最后一个皇后时,要执行的很明确,就是遍历位置,并判断有没有冲突,没有冲突,表示所有皇后的位置已经确定,输出state数组中存储的皇后位置,返回1,以供上层迭代判断,这里比较重要。
- 若有冲突,表示前面有皇后下错了,需要return出去,重设前面有错的皇后的位置
- 这部分代码如下:参数说明:
if (num - len == 1){//判断是否到最后一个皇后,是则: for (int position = 0; position < num; position++){ //遍历位置 if (!conflict(state, position, len)){//判断是否有冲突 state[len] = position;//没有冲突,记录位置到state printf("当前state:"); for (int i = 0; i <= len; i++){ printf("%d", state[i]);//输出 } printf("\n"); return 1; } } return 0; }
- 那如果准备下放的不是最后一个皇后呢?
- 同样的,这个皇后也需要遍历位置,如果没有冲突,则暂时将这个皇后下放,并记录位置到state
- 重点是:进入下一次迭代,即再次调用queens()模块,并判断其返回的值(与上文conflict部分相呼应),但是这次传入的参数:状态数组state已经是添加了上一个皇后的state,总皇后数num不变,但是已经下放的皇后数len+1。
- 现在我们假定,在上一点中进入的下一次迭代,有冲突,返回了0,则我们又跳回到本层来。这表明本层皇后的位置虽然不冲突,但是是错的,需要再次改变位置,即position+1,再次利用conflict()判断有没有冲突。这一部分,可以用continue解决掉(因为前面已经有相应代码和for循环)
- 这部分的确难理解,但是,程序到这里,基本就完了。
- 最好的理解办法还是:下断点,一步一步看。
- 这部分代码如下:
int queens(int num, int*state,int len) { if (num - len == 1){//判断是否到最后一个皇后,是则: for (int position = 0; position < num; position++){ //遍历位置 if (!conflict(state, position, len)){//判断是否有冲突 state[len] = position;//没有冲突,记录位置到state printf("当前state:"); for (int i = 0; i <= len; i++){ printf("%d", state[i]);//输出 } printf("\n"); return 1; } } return 0; } else{//不是最后一个皇后则: for (int position = 0; position < num; position++){ //遍历位置 if (!conflict(state, position, len)){ //判断是否有冲突,没有冲突则: //记录新皇后位置到state state[len] = position; if (queens(num, state, len + 1)){ //进入下次递归,并判断返回值 } else{ //如果下个皇后遍历全部位置都有冲突,返回到本层 //将本层的皇后位置+1,即继续遍历本层 continue; } } else //若本层当前位置有冲突,则继续遍历 continue; } return 0; } }
全部代码:
- 理解版:
#include<stdio.h> #include<math.h> int conflict(int* state, int nextx, int nexty) {//判断准备放下的后是否与之前的后有冲突 //state是储存之前皇后的位置数组, //nextx,nexty分别代表准备放下皇后的列和行 //判断的条件:不同列,即(state[i] - nextx)!=0 //不同行:由于nexty必定大于已有的行数,则必定不同行 //不在对角线上,先写出已知(x0,y0)的对角线方程: //y-y0=+-(x-x0); 即(nextx,nexty)不在直线上 //综上,条件如下: for (int i = 0; i < nexty; i++){ if ((abs(state[i] - nextx) == (nexty - i)) || \ (abs(state[i] - nextx) == 0)) return 1; } return 0; } int queens(int num, int*state,int len) { if (num - len == 1){//判断是否到最后一个皇后,是则: for (int position = 0; position < num; position++){ //遍历位置 if (!conflict(state, position, len)){//判断是否有冲突 state[len] = position;//没有冲突,记录位置到state printf("当前state:"); for (int i = 0; i <= len; i++){ printf("%d", state[i]);//输出 } printf("\n"); return 1; } } return 0; } else{//不是最后一个皇后则: for (int position = 0; position < num; position++){ //遍历位置 if (!conflict(state, position, len)){ //判断是否有冲突,没有冲突则: //记录新皇后位置到state state[len] = position; if (queens(num, state, len + 1)){ //进入下次递归,并判断返回值 } else{ //如果下个皇后遍历全部位置都有冲突,返回到本层 //将本层的皇后位置+1,即继续遍历本层 continue; } } else //若本层当前位置有冲突,则继续遍历 continue; } return 0; } } void main() { int *state[8]; queens(8, state, 0); system("pause"); }
2.精简版:
#include<stdio.h> #include<math.h> int conflict(int *state,int nextx,int nexty) { for(int i=0;i<nexty;i++){ if(fabs(nextx-state[i])==(nexty-i)||\ fabs(nextx-state[i])==0) return 1; } return 0; } int queens(int num,int *state,int len) { for(int position=0;position<num;position++){ if((num-len)==1){ if(!conflict(state,position,len)){ state[len]=position; printf("state:"); for(int x=0;x<=len;x++){ printf("%d",state[x]); } printf("\n"); return 1; } else{ continue; } } else{ if(!conflict(state,position,len)){ state[len]=position; if(queens(num,state,len+1)){ } } } } } void main() { int state[8]; queens(8,state,0); }
运行结果:四个皇后: 八个皇后: