骑士游历问题(C语言代码)

关于骑士游历问题,大家可以想到的方法是回溯法和贪心算法。回溯法的时间复杂度比较高,贪心算法的时间复杂度就好多了。

骑士游历问题

问题描述:

棋盘大小是8*8,骑士在棋盘任一方格开始游历。要求骑士游历棋盘的每一个方格且每个方格只游历一次。输出骑士的游历路径。

 

解决思路:

dir0、dir1…dir7由小到大排列,每次选择具有最少出口的方向,假设为dir0。如果骑士走到第n步时,没有出口可以选择,同时,骑士未完全遍历棋盘,那么骑士回溯到上一位置((n-1)步),选择dir1方向。骑士游历依次进行下去。直至走到终点或回到原点。终点代表骑士游历完成,原点代表骑士游历失败(没有可以游历每个方格游历且只游历一次的路径)。这里假设当前位置的八个方向已经按照“具有出口”由小到大排列。

 

编写程序过程要解决的问题(先考虑简单的问题):

一、每个位置最多有8个方向可以移动分别为:(-2,1)   (-1,2)   (1,2)   (2,1)   (2,-1)   (1,-2)   (-1,-2)   (-2,-1);

主函数中骑士在初始位置时,对个变量进行初始化代码:

step=1;
chessboard[startx][starty]=step;
step++;
cur_x=startx;
cur_y=starty;
dir[startx][starty]=max_dir;
last_dir=max_dir;

 

考虑到解决骑士游历需要用到多个函数。骑士前进、后退函数可能用到这八个方向移动的坐标。故将八个方向移动的坐标定义为全局变量,“int ktmove_x[max_dir],ktmove_y[max_dir];”。

二、判断骑士应该前进还是回溯:骑士在当前位置有可移动的方向则前进,反之,回溯到上一位置。八八棋盘上面的每个方格最多有八个方向可以移动。棋盘数组:“int chessboard[width][width];”,则可以定义一个三维数组来判断方格某一方向是否被访问过:“int is_visted[width][width][max_dir]={0};”,初始化为0,表示64个方格具有的出口方向(最多有八个)未被访问,两个数组同样是全局变量。

骑士前进函数

void forward()
{
	is_visted[cur_x][cur_y][last_dir]=1;//骑士当前位置下标是last_dir的方向已经试探过了
	cur_x+=ktmove_x[last_dir];
	cur_y+=ktmove_y[last_dir];
	chessboard[cur_x][cur_y]=step;//骑士前进成功,赋值棋盘当前位置的步数给骑士所在的棋盘方格
	step++;
	dir[cur_x][cur_y]=last_dir;//记录当前所在位置前进的方向,骑士需要回溯时使用。
	last_dir=max_dir;		//初始化last_direction
}

 骑士回溯函数:

void backward() { int i; step--; chessboard[cur_x][cur_y]=0; last_dir=dir[cur_x][cur_y];//将last_direction重置为上一位置到(curr_x,curr_y)所选择的方向 for(i=0;i<max_dir;i++) //把这个位置的所有可以动的方向重新置为未访问过 is_visted[cur_x][cur_y][i]=0; cur_x-=ktmove_x[last_dir]; cur_y-=ktmove_y[last_dir]; /* 回溯到上一位置,回溯到上一位置之后,在上一位置再试探时,应该从 last_direction+1的方向开始。参看成员函数select_direction()*/ } 

 三、确定骑士是完成游历还是回到原点:暂时使用“1” 与“0”分别表示完成游历与回到原点,无论骑士是完成游历或者回到原点都需要停止骑士的移动。骑士移动过程中,无法确定骑士到底需要前进多少次、回溯多少次。所以使用while()循环。但是,使用“1” 与“0”分别表示完成游历与回到原点,即while()循环进行下去的条件无论是0还是1都不符合。解决办法是将是否完成游历和是否回到原点分成2个功能函数:is_sucess(),1代表完成游历,0代表还未完成游历;int is_back_to_start(),1代表回到起始原点,0代表没有回到其实原点。那么,while()循环进行下去的条件就是:!is_end()&&!is_back_to_start(),既没有完成游历也没有回到原点。

判定骑士是否回到原点

int is_back_to_start()
{
	if(step==1)
		return 1;
	else
		return 0;
}

判定骑士是否到达终点

int is_end_sucess()
{
	if(step>width*width)
		return 1;
	else
		return 0;
}

 

四、这是最后一个也是最重要的问题:如何使用贪心法选定具有最少路径的方向

选定下一步是否有可以移动的方向,定义函数:select_dir(),定义局部变量count,使用两个for()循环分别计算当前位置周围的八个(最多有八个)位置的可移动方向。找出具有最小可移动方向的出口。

for(i=0;i<max_dir;i++)//对可能的八个方向的出口数目依次进行比较
{
	try_x=cur_x+ktmove_x[i];
	try_y=cur_y+ktmove_y[i];
	if(is_legal(try_x,try_y)==1&&!is_visted[cur_x][cur_y][i])//找出可选方向的出口数目
	{
		count=0;
		for(j=0;j<max_dir;j++)//判断每个出口(最多有八个出口)是否可用
		{
			next_x=try_x+ktmove_x[j];
			next_y=try_y+ktmove_y[j];
			if(is_legal(next_x,next_y)&&!is_visted[cur_x][cur_y][j])
				count++;			//出口可用,count自加1
		}
		if(count<min_dir)//类似于比较法判断最小值
		{
			last_dir=i;//将当前i值赋给last_direction,最大可以等于7
			min_dir=count;//将count赋值给min_dir,反之,min_dir大小保持不变
		}
	}
}

 代码中的关键是:变量count赋值给变量min_dir(count=min_dir)。

此外,还有一个函数,判定骑士是否游历成功的函数

int is_tourist_sucess()
{
	do
	{
		if(select_dir())
			forward();
		else
			backward();
	}
	while(!is_back_to_start()&&!is_end_sucess());//完成游历和回到原点需要分成两个函数
	if(is_back_to_start())
		return 0;//回到起点,骑士游历失败
	else
		return 1;//到达最后一步,骑士游历成功
}

到此为止,骑士游历问题结束。

优点:时间复杂度小,花费时间少。空间复杂度相对增加。
缺点:全局变量多,功能函数可移植性比较差。

其他:代码中step自加1和自减1的含义。158-160行为什么step赋值给chessboard[startx][starty]后,就要自加1?若不自加1,当骑士回溯到原点(但是还有可以试探的方向),重新选择可移动的方向时,step等于1,这样,程序就会误判骑士已回溯到原点且无可移动方向。

写代码过程,务必仔细。 我在写select_dir()函数时,不小心将if(is_legal(try_x,try_y)==1&&!is_visted[cur_x][cur_y][i]) 的代码写成:if(is_legal(try_x,try_y)==1&&is_visted[cur_x][cur_y][i]),检查半天也没找到错误。最后,用了近三个小时调试程序才找到这个错误。完整代码我已经上传到我的资源了,下载就行,使用的编程软件是VS2010。

    原文作者:八皇后问题
    原文地址: https://blog.csdn.net/wqk1014/article/details/8283325
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞