目录:
1. 八皇后问题的递归解法。
2. 八皇后问题的非递归解法。 回溯法
3. 跳马问题的递归解法。
八皇后和跳马问题都属于AI(人工智能)范畴,这两个问题比较简单,属于计算机穷举。
而且这两个问题又都属于搜索问题。就是试探并记录下试探的步伐,最后输出正确的结果。
先分析一下八皇后问题。
题目一看就是递归,因为问题描述可以表示为在第k层放好的情况下,放第k+1个皇后,使与
前面的不相冲突。
好,现在分析第k+1层。
1. 若k+1层==9, 表示搜索已经结束,打印结果,返回。
2. 否则,在8个行上试探,若本行,无皇后,正对角线无皇后,斜对角线无皇后,在该处放置皇后。
3. 递归到下一层。
问题虽然描述清了,但还不能写代码,还要继续分析细化。
1. 打印结果,那么结果是什么 ? 我们可以用1个2维数组来表示棋盘,当放上皇后用1表示,不放
皇后用0表示,很形象,对吗。打印的时候,你甚至可以打出图形来。
2. 怎样判断a[i][j]这个点能否放皇后,这是问题的关键。
四个条件,本行,本列,对角线和反对角线
可以简化为3个条件,把本列去掉,因为我们只在这列放一个。这列放不下,我们就回溯。我们
还要在这1到8行上逐一试探。
1. 要保证本列上无皇后。应该是调用一个函数。whether_this_row_has_queen(column). 返回布尔值。
不过这个函数还是要查寻一个数组,如果这个数组元素值为1,返回true, 否则,返回false.
这个数组,叫ROW_OCCUPY 数组。 8 个元素。 当本行放置皇后时,要置位该元素。
2. 更复杂的是要保证对角线上无皇后。 它可以通过调用whether_reverse_diagonal_has_queen(row,column)
先看反对角线吧。考察普通笛卡尔坐标下的棋盘。(0,0)第一条线
(1,0)(0,1)第二条线 (2,0)(1,1)(0,2)第三条线...(7,0)..(0,7)第8条线
row+column 是常数,继续(7,1)..(1,7)为第9条线,第(7,7)为第15条线。
所以,我们可以用一个reverse_diagonal[15] 来表示,谁占用了,把相应的标志位置上。
3. 正对角线。看函数whether_the_diagonal_has_queen(row,column)
看(0,0)(1,1)。。(7,7)构成一条线。这是中间。row=column, row-column=0
向两边扩散也有2n-1条对角线。左上(0,7),然后(0,6)(1,7)... 直到(6,0)(7,1)最后
(7,0).row-column 从-7 变到0再到+7, 为防止序号出现负数,可以加上一个基数7.
所以也可以设一个数组diagonal[15],占用了该对角线,
在该数组置标志。
3. 递归到下一层。 简单,层数加一就可以了,这个层数,可以用行号代替。
补充,逐行放而非逐列放是调试方便
据以上分析,整理数据结构和逻辑关系,可以写出c代码程序如下:
只有逻辑清楚,才能调试递归程序!!
错误的疏漏,会影响程序的逻辑! 不调试的程序,full of mistake and wrong typewords etc.
/* author: hjjdebug
* date : 2009
*/
#include <stdio.h>
#include <stdlib.h>
int ans[8][8];
int col_occupy[8];
int diagonal_occupy[15];
int r_diagonal_occupy[15];
void queen(int row);
int main(void)
{
queen(0);
system("pause");
return 0;
}
void queen(int row)
{
int i,j;
if(row==8)
{
char c;
for(i=0;i<8;i++)
{ //print result (diagraph)
for(j=0;j<8;j++)
{
printf("%c ",c=(ans[i][j]==1) ? '*':'-');
}
printf("/n");
}
printf("Quit(q) ? ");
c=getchar();
if(c=='q')
exit(1);
return ;
}
//try 8 position
int column;
for(column=0;column<8;column++)
{// if this position is valid,hold it, recursive to next layer
if(col_occupy[column]==0&&diagonal_occupy[7+column-row]==0&&r_diagonal_occupy[row+column]==0)
{
ans[row][column]=1;
col_occupy[column]=1;
diagonal_occupy[7+column-row]=1;
r_diagonal_occupy[row+column]=1;
queen(row+1);
// back state to try next position
ans[row][column]=0;
col_occupy[column]=0;
diagonal_occupy[7+column-row]=0;
r_diagonal_occupy[row+column]=0;
}
}
// when all try, return;
}
递归实际上属于深度优先搜索算法。可以利用函数栈存储数据和利用函数返回实现自动回退。
; --------------------------------------------------------------------------------------
递归是使用了函数栈,实际上是隐含回溯。 这里给一个不用递归用循环的程序,看其怎样实现回溯,如下例:
是的,非递归不如递归简洁。 但其进退之间别有一番情趣! 而且它还可以实现2级深度退却(依题意条件)。
还是迷宫退的深啊。
循环的退出条件是column 被逼回0,而不是通常的for 1 to 8 的结构,
有点新意, (有时间将详细介绍回溯的方法和思想) 下面程序不是很好,聊作参考吧。(有空再整理)
#include <stdio.h>
#include <stdlib.h>
#define MAXN 20
int good;
int ans[MAXN+1];
int a[MAXN+1];
int b[2*MAXN+1];
int c[2*MAXN+1];
int main()
{
int j;
int column=1;
int last_column=8;
for (j=0;j<=last_column;j++) a[j]=1;
for (j=0;j<=2*last_column;j++) b[j]=c[j]=1;
ans[1]=1;good=1;ans[0]=0;
do{
if (good)
{
if (column==last_column)
{ // print result
for (j=1;j<=last_column;j++)
{
printf("%d ",ans[j]);
}
printf("/n");
while (ans[column]==last_column)
{
column--; /*清除关于第column列,ans[column]行有皇后标志*/
a[ans[column]]=b[column+ans[column]]=c[last_column+column-ans[column]]=1;
}
ans[column]++; /*调整第column列的皇后配置*/
}
else /*在第column列,ans[column]行位置设定有皇后标志*/
{
a[ans[column]]=b[column+ans[column]]=c[last_column+column-ans[column]]=0;
ans[++column]=1; /*第column+1列,从第1行开始配置*/
}
}
else
{
while (ans[column]==last_column)
{
column--; /*清除关于第column-1列、ans[column-1]行有皇后标志*/
a[ans[column]]=b[column+ans[column]]=c[last_column+column-ans[column]]=1;
}
ans[column]++; /*调整第column列的皇后配置*/
}
good=a[ans[column]]&&b[column+ans[column]]&&c[last_column+column-ans[column]];
} while (column!=0); //column 从第1行开始,最后被逼回0行,表示完全搜索结束。
system("pause");
return 0;
}