这一篇文章,我将送你八个妹子陪你玩儿~

别想歪了,我指的是八皇后问题。
回溯法:如何看遍人生的种种可能?

一、从人生的可能说起
你的人生有多少种可能?

如果存在平行宇宙的话,我想应该是无数种。而活在这个宇宙的你,需要从无数的可能中选出一个,只能说,生而为人,实在不易。

那有没有一种方法,能让你尝遍所有人生的滋味,然后再作抉择,选出你最想要的人生呢?

这种方法虽然不存在,却又近似的替代方法:阅读。大量的阅读,去品尝优秀作家所体验到的人生百态,然后,选择少有人走的路。

相信通过这样的方法走出来的人生,必然不会让你后悔。

虽然人生无法遍历,但在编程中,有一种算法能够满足我们遍历所有可能的方法。

请记住这个神奇方法的名字:回溯法

二、向经典的例题学习
沿袭传统,废话不多说,直接上题:
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》)
回溯法:如何看遍人生的种种可能?
不妨去看看吧~
回溯法:如何看遍人生的种种可能?

我的读书公众号~欢迎关注~

相关文章:

  • 2021-06-04
  • 2022-02-18
  • 2021-11-12
  • 2022-12-23
  • 2021-10-05
猜你喜欢
  • 2021-11-14
  • 2021-11-23
  • 2022-12-23
  • 2021-10-23
  • 2022-02-28
  • 2021-11-30
相关资源
相似解决方案