数据结构学习笔记(12.递归的应用之八皇后回溯算法)

本节知识点:

1.递归与回溯:
   a.回溯算法的基本思想:从问题的某一种状态出发,搜索可以到达的所有状态。当某个状态到达后,可向前回退,并继续搜索其他可达状态。当所有状态都到达后,回溯算法结束!
   b. 对于回溯算法,在前面KMP匹配中就利用了这个思想,只不过当时KMP中定义了一个node数组(起到了一个地图的作用,记录了每种回溯情况的可能)。而这节中,是利用函数的活动对象保存回溯算法的状态数据,因此可以利用递归完成回溯算法!
2.八皇后问题:
   a.问题背景:国际象棋是一个8*8的矩阵,在棋盘中同时放下8个皇后,且互相不攻击的情况叫八皇后问题
   b.如图,说明八皇后问题中的回溯算法:
      
注意:其实就是不断的通过递归函数,去往棋盘中尝试放皇后,成功就继续递归(即继续放皇后),失败就跳出递归函数,回溯到上层递归函数中,上层递归函数中保存着上一个皇后的位置!!!这就是八皇后中,回溯的概念!
    c. 八皇后的算法思路:
       第一、为了更加方便我们表示棋盘,我们使用一个10*10的二维数组来表示8*8的棋盘加棋盘边框。
char board[10][10];
       第二、初始化棋盘,把棋盘边框标记为'#',把棋盘中的位置都标记为‘ 空格 ’
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] = ' ';
        }
	}
}
       第三、定义一个棋盘的打印函数display(),方便测试
void display()
{
    int i = 0;
    int j = 1;
    
    for(i=0; i<N+2; i++)
    {
        for(j=0; j<N+2; j++)
        {
            printf("%c", board[i][j]);
        }
        
        printf("\n");
    }
}
      第四、 我们定义一个检查的函数check(),来判断这个位置是否可以放皇后,对于检查,我们检查三个方向,左上(-1,-1) 、 右上(-1,1)  、正上(-1,0)   对于横排我们是不检测的,因为我们每一排就放一个皇后,皇后用' * '来表示!
typedef struct _tag_pos //定义一个数据结构来充当方向 
{
	int i;
	int j;
}Pos;
/*检测三个方向  左上  右上  正上   横排是不检测的 因为一排只放一个*/ 
static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};

int check(int i, int j)
{
	int p = 0; 
	int ret = 1;
	for(p = 0; p < 3; p++) //检测三个方向 
	{
		int ni = i;
		int nj = j;
		
		while(ret && (board[ni][nj] != '#'))//判断没有到达棋盘边界 
		{
			ni = ni + pos[p].i;
			nj = nj + pos[p].j;
			
			ret = ret && (board[ni][nj] != '*');//判断这个方向没有放过皇后 
		}
	} 
	return ret; //可以放皇后返回1  不可返回0 
}
      第五、 是八皇后算法的核心,就是放皇后的函数find(),这个函数是这样的!先从第一行开始,进行对列数j的for循环,在for循环中判断(i,j)是否可以放皇后,一旦可以放皇后,就放置一个皇后,然后find(i+1),进入第二行(即第二层递归)。继续做同样的判断,这个递归函数有两个出口,同理,就有两种情况结束递归,一种是,i超过了第八行,也就是说,皇后全部放置完全,所以应该display()打印!另一种是,for循环结束,依然没有check()成功,说明前面有皇后放错了!导致这一行不能放皇后!应该进行回溯!!!所以结束这个错误的递归,返回到上一层递归,清除上层递归中放错的皇后   board[i][j] = ' ';  继续进行上一行的for循环!!!这里需要补充的是,即使i超过了第八行,皇后放置完全了,函数依然也会返回到上一行,去进行上一行的for循环,擦除皇后位置,尝试寻找新八皇后的情况!!!
void find(int i)
{
	int j = 0;
	if(i > N) //判断是否已经超过了第八行 
	{
		coust++; //计算八皇后情况的个数 
		display(); 
		//getchar();
	}
	else
	{
		for(j = 1; j <= N; j++) //判断一行 是否有匹配的位置 
		{
			if(check(i,j))
			{
				board[i][j] = '*'; //放置皇后 
				find(i+1);
				board[i][j] = ' '; //清除放错的皇后 
			}
		}
	}
} 
注意:之所以说,八皇后中利用递归来进行回溯,就是因为当递归结束的时候,会返回到上一层放置皇后的位置(即栈空间中,保存了上行皇后i,j的情况)
看着上面的动态想象下,当有时候,出现过的皇后又消失了,就是因为for循环结束了,还没有check成功,导致递归结束,返回到上层递归,并且擦除上层皇后的位置,并且继续for循环,如果这层for中check依然不成功,继续回溯,这个消失的过程就是回溯过程!与动图不同的是,我们的程序,不会在找完一种情况后就结束!即使成功匹配,程序也会向上层回溯,去寻找其它的情况,直到第一行for循环结束(即 i=1 的时候),程序才会停止!

本节代码:

八皇后问题的完整代码:
#include <stdio.h>

#define N 8

static char board[N+2][N+2];
static int coust = 0; //记录八皇后个数 
typedef struct _tag_pos //定义一个数据结构来充当方向 
{
	int i;
	int j;
}Pos;
/*检测三个方向  左上  右上  正上   横排是不检测的 因为一排只放一个*/ 
static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};

 
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 = 1;
    
    for(i=0; i<N+2; i++)
    {
        for(j=0; j<N+2; j++)
        {
            printf("%c ", board[i][j]);
        }
        
        printf("\n");
    }
}

int check(int i, int j)
{
	int p = 0; 
	int ret = 1;
	for(p = 0; p < 3; p++) //检测三个方向 
	{
		int ni = i;
		int nj = j;
		
		while(ret && (board[ni][nj] != '#'))//判断没有到达棋盘边界 
		{
			ni = ni + pos[p].i;
			nj = nj + pos[p].j;
			
			ret = ret && (board[ni][nj] != '*');//判断这个方向没有放过皇后 
		}
	} 
	return ret; //可以放皇后返回1  不可返回0 
}
 
void find(int i)
{
	int j = 0;
	if(i > N) //判断是否已经超过了第八行 
	{
		coust++; //计算八皇后情况的个数 
		display(); 
		//getchar(); //起到断点的作用 
	}
	else
	{
		for(j = 1; j <= N; j++) //判断一行 是否有匹配的位置 
		{
			if(check(i,j))
			{
				board[i][j] = '*'; //放置皇后 
				find(i+1);
				board[i][j] = ' '; //清除放错的皇后 
			}
		}
	}
} 
int main()
{
	init();
	//display();
	find(1); 
	printf("coust is %d \n",coust);
	return 0;
} 
注意:八皇后问题,一共有92种情况!

课后思考:

 本节遗留了两个问题没有解决:
 1.对于全排列中有重复字母的情况,本节代码是解决不了的,会出现重复的情况,但是我觉得解决这个问题,就不应该使用递归思想去处理全排列了!!!
 2.对于回溯算法,可以在迷宫寻找出口的程序中得到很好的应用,值得思考下!





  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值