本文部分内容转载自:八皇后问题(递归回溯算法详解+C代码)作者:苍之羽 仅作个人学习使用
一、什么是回溯法?
按选优条件向前搜索,以达到目标。
但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术就叫回溯法。
二、回溯法和递归的联系:
我们在回溯法中可以看到有递归的身影,但是两者是 完全不同、却有着联系 的两个概念。
回溯法从问题本身出发,寻找可能实现的所有情况。
和穷举法的思想相近,不同在于穷举法是将所有的情况都列举出来以后再一一筛选,而回溯法在列举过程如果发现当前情况根本不可能存在,就停止后续的所有工作,返回上一步进行新的尝试。
递归是从问题的结果出发,例如求 n!,要想知道 n!的结果,就需要知道 n*(n-1)! 的结果,而要想知道 (n-1)! 结果,就需要提前知道 (n-1)*(n-2)!。这 回溯和递归唯一的联系就是,回溯法可以用递归思想实现。
三、八皇后问题分析
八皇后太多了,我们先用四皇后,来讲解一下:
(1) 我们把第一个皇后放在第一个格子,被涂黑的地方是不能放皇后的:
(2) 第二行的皇后只能放在第三格或第四格,比如我们放在第三格:
前面两位皇后,已经把第三行全部锁死了,第三位皇后无论放哪里都难逃被杀掉的厄运。
(3) 于是在第一个皇后位于1号,第二个皇后位于3号的情况下问题无解。我们只能返回上一步来,给2号皇后换个位置。
(4) 第三个皇后只有一个位置可选。当第三个皇后占据第三行蓝色空位时,第四行皇后无路可走,于是发生错误,返回上层调用(3号皇后),而3号也别无可去,继续返回上层调用(2号),2号已然无路可去,继续返回上层(1号),于是1号皇后改变位置如下,继续搜索。
四、代码实现:
void EightQueen( int row )
{
int col;
if( row>7 ) //如果遍历完八行都找到放置皇后的位置则打印
{
Print(); //打印八皇后的解
count++;
return ;
}
for( col=0; col < 8; col++ ) //回溯,递归
{
if( notDanger( row, col ) ) // 判断是否危险
{
chess[row][col]=1;
EightQueen(row+1);
chess[row][col]=0; //清零,以免回溯时出现脏数据
}
}
}
我们来重点看一下这段代码:
第一次进来,row=0,意思是要在第一行摆皇后,只要传进来的row参数不是8,表明还没出结果,就都不会走if里面的return,那么就进入到for循环里面,col从0开始,即第一列。此时第一行第一列肯定合乎要求(即notDanger方法肯定通过),能放下皇后,因为还没有任何其他皇后来干扰。
关键是notDanger方法通过了之后,在if里面又会调用一下自己(即递归),row加了1,表示摆第二行的皇后了。第二行的皇后在走for循环的时候,分两种情况,第一种情况:for循环没走到头时就有通过notDanger方法的了,那么这样就顺理成章地往下走再调用一下自己(即再往下递归),row再加1(即摆第三行的皇后了,以此类推)。第二种情况:for循环走到头了都没有通过notDanger方法的,说明第二行根本一个皇后都摆不了,也触发不了递归,下面的第三行等等后面的就更不用提了,此时控制第一行皇后位置的for循环col加1,即第一行的皇后往后移一格,即摆在第一行第二列的位置上,然后再往下走,重复上述逻辑。
注意,一定要添加清零的代码,它只有在皇后摆不下去的时候会执行清0的动作(避免脏数据干扰),如果皇后摆放很顺利的话从头到尾是不会走这个请0的动作的,因为已经提前走if里面的return方法结束了。
总之,这段核心代码很绕,原理一定要想通,想个十几二十遍差不多就能理解其中的原理了,递归回溯的思想也就不言而喻了。八皇后问题一共有92种情况
完整代码如下:
#include <stdio.h>
int count = 0;
int chess[8][8]={0};
int notDanger( int row, int col )
{
int i,k;
// 判断列方向
for( i=0; i < 8; i++ )
{
if( chess[i][col]==1 )
{
return 0;
}
}
// 判断左对角线
for( i=row, k=col; i>=0 && k>=0; i--, k-- )
{
if(chess[i][k]==1 )
{
return 0;
}
}
// 判断右对角线
for( i=row, k=col; i>=0 && k<8; i--, k++ )
{
if(chess[i][k]==1 )
{
return 0;
}
}
return 1;
}
void Print() //打印结果
{
int row,col;
printf("第 %d 种\n", count+1);
for( row=0; row < 8; row++ )
{
for( col=0; col< 8; col++ )
{
if(chess[row][col]==1) //皇后用‘0’表示
{
printf("0 ");
}
else
{
printf("# ");
}
}
printf("\n");
}
printf("\n");
}
void EightQueen( int row )
{
int col;
if( row>7 ) //如果遍历完八行都找到放置皇后的位置则打印
{
Print(); //打印八皇后的解
count++;
return ;
}
for( col=0; col < 8; col++ ) //回溯,递归
{
if( notDanger( row, col ) ) // 判断是否危险
{
chess[row][col]=1;
EightQueen(row+1);
chess[row][col]=0; //清零,以免回溯时出现脏数据
}
}
}
int main()
{
EightQueen(0);
printf("总共有 %d 种解决方法!\n\n", count);
return 0;
}