这一篇文章,我将送你八个妹子陪你玩儿~
别想歪了,我指的是八皇后问题。
一、从人生的可能说起
你的人生有多少种可能?
如果存在平行宇宙的话,我想应该是无数种。而活在这个宇宙的你,需要从无数的可能中选出一个,只能说,生而为人,实在不易。
那有没有一种方法,能让你尝遍所有人生的滋味,然后再作抉择,选出你最想要的人生呢?
这种方法虽然不存在,却又近似的替代方法:阅读。大量的阅读,去品尝优秀作家所体验到的人生百态,然后,选择少有人走的路。
相信通过这样的方法走出来的人生,必然不会让你后悔。
虽然人生无法遍历,但在编程中,有一种算法能够满足我们遍历所有可能的方法。
请记住这个神奇方法的名字:回溯法
二、向经典的例题学习
沿袭传统,废话不多说,直接上题:
2698:八皇后问题总时间限制: 10000ms 内存限制: 65536kB描述在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。输入无输入。输出按给定顺序和格式输出所有八皇后问题的解(见Sample Output)。样例输入无样例输出
No. 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 No. 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 No. 3 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 No. 4 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 No. 5 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 No. 6 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 No. 7 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 No. 8 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 No. 9 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 ...以下省略
简单来说,8个皇后摆在棋盘上,每行、每列、每斜线上允许有一个皇后。
解题思路也很简单,把八个皇后所有可能摆放的位置全部都尝试一边,然后找出符合题目要求的,输出即可。具体的实施方式是:构造一个递归函数,这个递归函数可以遍历整个棋盘,在每放下一个皇后时,对相应的位置作标记(详见1、如何落子)并调用自身,在下一个递归中寻找下一个合适的位置,最关键的地方在于,调用自身后,要取消之前做过的位置标记,就像悔棋一样,回到做决定前的状态,以便尝试其他的可能——这就是回溯法的关键!
这个递归函数将会有这样的结构:
void Func()
{
for(;;)
{
mark;
Func();
unmark;
}
}
此结构也就是回溯法的核心。
另外,注意到本题的输出要求,要按照给定的的顺序和格式输出,分析Sample Out可以发现,给出的解,是按照从左列往右,从上到下的方式尝试,并输出解的。
分析完毕,接下来介绍解题的几个关键问题和代码的实现方式:
1、如何落子?
一旦某一格放置了一个皇后,那么,相应格子的行、列、斜线方向都不能落子。这个问题我们可以用标记变量来解决,每落一个子,相应的格子、行、列、左斜方向、右斜方向都从0标记为1,代表相应的位置不能再落子。
int board[8][8]{};
int rows[8]{};
int cols[8]{};
int leftdiagonal[15]{};
int rightdiagonal[15]{};
2、如何按题目要求的顺序输出答案?
j代表列、i代表行,从左往右,从上往下地尝试落子:
for(int j=c;j<8;j++)
{
for(int i=(j==c)?r:0;i<8;i++)
{
}
}
注意:int i=(j==c)?r:0; r,c代表上一个皇后的位置,这条语句的意思是是,上一个皇后之前的位置都已经尝试过了,无法落子,所以不必要搜索,从上一个皇后的位置开始搜索即可。
3、如何判定是否找到了符合要求的解?
既然皇后是按照题目要求一个个落子的,那么,只要放置的皇后数目达到了八个,那么就是题目的解
接下来是完整的AC Code
#include <iostream>
using namespace std;
int board[8][8]{};
int rows[8]{};
int cols[8]{};
int leftdiagonal[15]{};
int rightdiagonal[15]{};
bool isok(int r,int c);
void func(int n,int r,int c);
void ptintboard();
int main(int argc, const char * argv[]) {
func(8,0,0);
}
void func(int n,int r,int c)
{
if(n==0)
{
ptintboard();
}
for(int j=c;j<8;j++)
{
for(int i=(j==c)?r:0;i<8;i++)
{
if(isok(i,j))
{
board[i][j]=1;
leftdiagonal[i+j]=1;
rightdiagonal[i-j+7]=1;
rows[i]=1;
cols[j]=1;
func(n-1,i,j);
board[i][j]=0;
leftdiagonal[i+j]=0;
rightdiagonal[i-j+7]=0;
rows[i]=0;
cols[j]=0;
}
}
}
}
bool isok(int r,int c)
{
return !board[r][c]&&!leftdiagonal[r+c]&&!rightdiagonal[r-c+7]&&!rows[r]&&!cols[c];
}
int no=1;
void ptintboard()
{
cout<<"No. "<<no<<endl;
++no;
for(int i=0;i<8;i++)
{
for(int j=0;j<8;j++)
{
cout<<board[i][j]<<" ";
}
cout<<endl;
}
}
三、题外话:电影推荐
关于人生的种种可能的探讨,其实一直都是艺术家哲学家的思考主题,有一部电影正是基于此主题的思考:
《无姓之人》(《Mr. NOBODY》)
不妨去看看吧~
我的读书公众号~欢迎关注~