回溯法,简单理解就是有源可溯。基本思想要借鉴穷举法,但是它不是一味地穷举,当发现某一步不符合条件时,这一步后面的穷举操作就不进行了(俗称“剪枝”),我自己把它叫做动态穷举法。假设第一个步骤可行,那么执行第二个步骤,第三个......如果其中第三个步骤不行,那么我们再回过来(回溯),第二个步骤换一种方法尝试,然后再重新第三个步骤,第四个......直到完成任务要求为止。
这里,以八皇后问题为例。试图把回溯法讲清楚。
注意:递归应该是一种算法结构,回溯法是一种算法思想。
何为八皇后问题?
(百度百科)八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当 n = 1 或 n ≥ 4 时问题有解。
八皇后问题最早是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出。之后陆续有数学家对其进行研究,其中包括高斯和康托,并且将其推广为更一般的n皇后摆放问题。八皇后问题的第一个解是在1850年由弗朗兹·诺克给出的。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。
艾兹格·迪杰斯特拉在1972年用这个问题为例来说明他所谓结构性编程的能力。
八皇后问题出现在1990年代初期的著名电子游戏第七访客中。
解题思想
我们采用回溯法来解决这个问题。还记得我说的动态穷举法(“剪枝”)?那么我们就开始穷举吧。过程请看下图。八个皇后,每个皇后放一行,那么我们要确定的就是每行皇后要放的列的位置。对于第一行,假设把皇后放在第一列(这里就开始了一个for循环了)。第一步当然满足,然后我们看第二行(又开始一个for循环啦),假设把第二个皇后放在(2,1)(行,列)处,不行(“剪枝”)!那继续for,放在(2,2)处,不行(“剪枝”)!继续for,放在(2,3)处。Bingo!那我们进行第三步,第四步....也许,在第三步当中,执行完第三步的八次for循环后,仍未有合理的答案。那么就得返回第二步了。这时候,把第二个皇后放在(2,4)处。继续......
(参考http://blog.csdn.net/justme0/article/details/7540425)
接下来,我们就要尝试把这个过程转化成伪代码。
//寻找当前行的皇后应该位于哪一列 void FindQueen(row) { for(int i=0;i<8;i++) { //1.判断当前是否满足要求 if(Is_Meet(row,i)) { //2.判断当前是否是最后一行了 if(最后一行){输出操作,并返回} //3.执行下一行匹配 FindQueen(row+1); //4.如果进行到这一步,说明步骤三已经操作完,没有合适结果,需要返回上一步,即执行当前for的下一个循环 } } }
判断是否满足条件
这里,有一步我们需要再考虑下的,即第1步,如何判断当前插入的皇后满足条件。
根据国际象棋的规则,不能有两个皇后位于:1)同一列;2)同一对角线。
1)同一列
比较好判断,列号相同,即属于同一列;
2)同一对角线
有两种位置:正斜对角线和反斜对角线。判断条件分别为:(r1+c1)==(r2+c2),(r1-c1)==(r2-c2)
将新的皇后位置依次与已经插入过的皇后位置进行比较判断即可。
具体实现
看到以上代码,应该有点思路了吧。上具体实现代码。
bool Is_Meet(int row,int column) { int c; for(int r=0;r<row;r++) { c=queen[r]; if(column==c) return 0; if((row+column)==(c+r)) return 0; if((column-row)==(c-r)) return 0; } return 1; } void findQueen(int row) { for(int c=0;c<8;c++) { if(Is_Meet(row,c)) { queen[row]=c; if(row==7) { print(); return; } findQueen(row+1); } queen[row]=-1; } }
为了好玩,简单地改了下,使其可以在控制台动态显示匹配过程。
#include "iostream" #include "string" #include <Windows.h> using namespace std; int queen[8]; void print() { system("cls"); cout << "八皇后问题动态演示\n"; cout<< "------------------------\n"; for (int outer = 0; outer < 8; outer++) { if(queen[outer]!=-1) { for (int inner = 0; inner < queen[outer]; inner++) cout << " . "; cout<<" # "; } for (int inner = queen[outer] + 1; inner < 8; inner++) cout << " . "; cout<<endl; } } bool Is_Meet(int row,int column) { int c; for(int r=0;r<row;r++) { c=queen[r]; if(column==c) return 0; if((row+column)==(c+r)) return 0; if((column-row)==(c-r)) return 0; } return 1; } void findQueen(int row) { for(int c=0;c<8;c++) { Sleep(1000); print(); if(Is_Meet(row,c)) { queen[row]=c; if(row==7) { //print(); return; } findQueen(row+1); } queen[row]=-1; } } int main() { memset(queen,-1,8*sizeof(int));//这里是赋-1,故不会出错,要清楚memset是依次对单个字节进行赋值 findQueen(0); }