前言:
八皇后问题,甚至连我远房表弟都知道的计算机界经典问题,让我们看看怎么写吧
还是一样的,不想看分析的懒懒直接翻到最后抄代码就OK
思路
我们就题言题,这道题奶奶滴我在网上没找到一模一样的,找到的基本都是八皇后问题一共能有多少种解,真晦气。。咳咳,这道题,可能第一眼思路是用循环去遍历所有可能性,不过显然,这是一种非常非常不可行的方法,因为我们不知道第二个棋子的落子位置(其实吧试一试还是能试出来的,节目效果/doge)。说到这句话,有没有一点体会呢,我们不知道第二个棋子的位置,于是我们便应该将判断第二个棋子位置的任务就交给计算机了。
很好,假设我们已经让计算机判断成功第二个棋子的位置了,然后呢???
ANS:然后我们应该让计算机去尝试判断第三个棋子的位置
太对啦,我们应该让计算机去判断第三个棋子的位置,于是,大家觉不觉得这很像递归呢?实际上确实是这样,尽管我们知道这道题用的叫做回溯算法,可是其本质依旧是比较基础的递归。
让我们开始做题
首先初始化棋盘,我们没有必要使用8x8的数组来存储,因为经过我们聪明的脑瓜子思考,某一列有了一个棋子以后,这一列就不能放第二个棋子了!!get it!我们使用行、列、从右上到左下、从左上到右下四个数组来表示这个棋盘。全部初始化为0,表示全部无棋子。
已经落子的一列或者一行或者啥啥的,我们将数组值变为1
int x[9] = { 0 }; //行 用下标从1-8
int y[9] = { 0 }; //列 用下标从1-8
int leading_diagonal[16] = { 0 };//主对角线 用下标从1-15
int counter_diagonal[16] = { 0 };//副对角线 用下标从1-15
构造递归函数,大体思路其实很简单,让我简单的说一说:
我们选择从第一行开始进行判断,具体细节我放代码注释里了
void put_queen(int n, int m)//n是已有的皇后数量,m是最大皇后数量
{
//函数出口,当我们每一行都放上一个棋子,也就是8个棋子时, 结束函数,计数器+1
if (n == m) {
++count;
return;
}
//函数递归部分
else
{
for (int i = 1; i <= 8; i++)//从第一行开始,一行一行的遍历
{
if (x[i] == 1)
continue;//如果已经落子了,我们不予执行,继续判断下一行是否可行
for (int j = 1; j <= 8; j++) //一列一列尝试
{
//下面这个确定主(副)对角线方向的坐标其实是需要一定计算的,题主当时想了好久,找找规律也行叭
if (y[j] == 0 && leading_diagonal[i - j + 8] == 0 && counter_diagonal[i + j - 1] == 0)
{
//若满足落子条件,落子
x[i] = 1;
y[j] = 1;
leading_diagonal[i - j + 8] = 1;
counter_diagonal[i + j - 1] = 1;
put_queen(n + 1, m);//进入下一层,判断下一个棋子
//下面的就是回溯,出来以后将上一次落子的棋子取消掉,判断下一列 (这不是下一层奥,这边不懂的话重新理解理解递归)
x[i] = 0;
y[j] = 0;
leading_diagonal[i - j + 8] = 0;
counter_diagonal[i + j - 1] = 0;
}
else//不能落子的话,尝试下一列落子
continue;
}
break;//这个break是必要的
/* 不然在你回溯到上一层,并且每一列都尝试过不行以后,我们希望的是回到上一行进行落子,如果没有这个break的话
循环会忽略上一行的落子已经不行了这个事实,到下一行进行落子,于是便出现了错误 (终于想出来怎么讲了····)*/
}
}
return;
}
这边的回溯出来其实就是回到了上一层落子,回溯的精髓
主函数的初始化数据啥的根据题目来的,没什么难度,整体代码放下头了
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int x[9] = { 0 }; //行 用下标从1-8
int y[9] = { 0 }; //列 用下标从1-8
int leading_diagonal[16] = { 0 };//主对角线 用下标从1-15
int counter_diagonal[16] = { 0 };//副对角线 用下标从1-15
int count = 0;//总可能性
void put_queen(int n, int m)//n是已有的皇后数量,m是最大皇后数量
{
//函数出口,当我们每一行都放上一个棋子,也就是8个棋子时, 结束函数,计数器+1
if (n == m) {
++count;
return;
}
//函数递归部分
else
{
for (int i = 1; i <= 8; i++)//从第一行开始,一行一行的遍历
{
if (x[i] == 1)
continue;//如果已经落子了,我们不予执行,继续判断下一行是否可行
for (int j = 1; j <= 8; j++) //一列一列尝试
{
//下面这个确定主(副)对角线方向的坐标其实是需要一定计算的,题主当时想了好久,找找规律也行叭
if (y[j] == 0 && leading_diagonal[i - j + 8] == 0 && counter_diagonal[i + j - 1] == 0)
{
//若满足落子条件,落子
x[i] = 1;
y[j] = 1;
leading_diagonal[i - j + 8] = 1;
counter_diagonal[i + j - 1] = 1;
put_queen(n + 1, m);//进入下一层,判断下一个棋子
//下面的就是回溯,出来以后将上一次落子的棋子取消掉,判断下一列 (这不是下一层奥,这边不懂的话重新理解理解递归)
x[i] = 0;
y[j] = 0;
leading_diagonal[i - j + 8] = 0;
counter_diagonal[i + j - 1] = 0;
}
else//不能落子的话,尝试下一列落子
continue;
}
break;//这个break是必要的
/* 不然在你回溯到上一层,并且每一列都尝试过不行以后,我们希望的是回到上一行进行落子,如果没有这个break的话
循环会忽略上一行的落子已经不行了这个事实,到下一行进行落子,于是便出现了错误 (终于想出来怎么讲了····)*/
}
}
return;
}
int main()
{
int qipan[9][9] = { 0 };
for (int i = 1; i <= 8; i++)
for (int j = 1; j <= 8; j++)
scanf("%d", &qipan[i][j]);
int num_queen = 0;//我们这题初始棋子数字不知道,我们要用计数器记住,并传递到回溯函数中
for (int i = 1; i <= 8; i++)
{
for (int j = 1; j <= 8; j++)
{
if (qipan[i][j] == 1)
{
++num_queen;
x[i] = 1;
y[j] = 1;
leading_diagonal[i - j + 8] = 1;
counter_diagonal[i + j - 1] = 1;
}
}
}
put_queen(num_queen, 8);
printf("%d\n", count);
return 0;
}
后记:
受益的懒懒们点个赞叭(鞠躬
这边还有一道回溯的题目,相比之下八皇后这经典题目反而要难一点,链接放下头,也是OJ里的题目,有兴趣可以去看看