1、递归与回溯
- 递归在程序设计中也常用于需要回溯算法的场合
- 回溯算法的基本思想
- 从问题的某一种状态出发,搜索可以到达的所有状态
- 当某个状态到达后,可向前回退,并继续搜索其它可达状态
- 当所有状态都到达后,回溯算法结束
- 程序设计中可利用函数的活动对象保存回溯算法的状态数据,因此可以利用递归完成回溯算法
2、八皇后问题
在一个8×8国际象棋盘上,有有8个皇后,每个皇后占一格;要求皇后间不会出现相互“攻击”的现象,即不能有两个皇后处在同一行、同一列或同一对角线上。
3、解决方案
算法思路
- 初始化:i = 1
- 初始化:j = 1
- 从第i行开始,恢复j的当前值,判断第j个位置
a.位置j可放入皇后:标记位置(i, j),i++ ,转步骤2
b. 位置j不可放入皇后:j++,转步骤a
c. 当j>8,i– ,转步骤3 - 结束:
第8行有位置可放入皇后
#include <stdio.h>
#include <stdbool.h>
//定义棋盘,加边界
#define N 8
static char board[N+2][N+2];
//坐标移动的偏移量
typedef struct _struct_Pos
{
int ios;
int jos;
}Pos;
Pos pos[3] = {{-1,-1},{-1,0},{-1,1}};
/*
初始化表格,表格有一个边框,存放着字符'#'
1-8行、列初始化为空 ' '
*/
void init()
{
int i = 0;
int j = 0;
for(i = 0; i < N+2;i++)
{
board[0][i] = '#';
board[N+1][i] = '#';
board[i][0] = '#';
board[i][N+1] = '#';
}
for(i = 1;i<=N;i++)
{
for(j = 1;j<=N;j++)
{
board[i][j] = ' ';
}
}
}
/*
用于显示“棋盘”(二维数组)
*/
void Display()
{
int i = 0;
int j = 0;
for(i = 0;i<N+2;i++)
{
for(j = 0;j<N+2;j++)
{
printf("%c\t",board[i][j]);
}
printf("\n");
}
}
/*
检查i行,j列这个位置是否可以插入“皇后”
因为插入的顺序是从上而下的(i=1 --> i=N ),所以检查是否可以插入皇后时,
可只检查插入位置的三个方向的上半部分位置有没有皇后存在,避免多余的计算量,
这三个方向的每次位置移动的偏移量定义在了Pos结构数组中
*/
bool check(int i,int j)
{
bool ret = true;
int p = 0;
//检查三个不同的方向
for(p = 0;p<3;p++)
{
int ci = i;
int cj = j;
while(ret && (board[ci][cj] != '#'))
{
ci = ci + pos[p].ios;
cj = cj + pos[p].jos;
//每移动到新的位置,都判断是否存在 ”皇后“
ret = ret && (board[ci][cj] != '*');
}
}
return ret;
}
/*
查找位置,放入皇后
*/
void find(int i)
{
int j = 0;
//如果第八行成功插入皇后,那么说明找到了一种方法,可以输出了
//否则,表示还未成功
if(i > N)
{
Display();
//getchar();
}
else
{
/*
在每一行中查找可插入”皇后“的位置,如果可以插入,就暂时放上皇后,
然后递归执行下一行,直到i>8,输出结果,如果检查失败,即某一行无法
插入”皇后“,则说明上一行”皇后“的位置有问题,此时check失败,导致
当前递归函数结束,返回上一级递归函数继续执行,然后执行board[i][j] = ' ';
将此行的”皇后“移出,继续查找可插入的位置,依此类推,直到第八行成功的插入了皇后
*/
for(j = 1; j<=N;j++)
{
if(check(i,j))
{
board[i][j] = '*';
find(i+1);
board[i][j] = ' ';
}
}
}
}
int main()
{
init();
find(1);
return 0;
}
4、小结
- 回溯算法是递归应用的重要场合
- 利用函数调用的活动对象可以保存回溯算法中重要的变量信息