您的位置:首页 > 编程语言 > C语言/C++

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

2012-12-14 07:05 190 查看
关于骑士游历问题,大家可以想到的方法是回溯法和贪心算法。回溯法的时间复杂度比较高,贪心算法的时间复杂度就好多了。

骑士游历问题
问题描述:

棋盘大小是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];”,则可以定义一个三维数组来判断方格某一方向是否被访问过:“[b]int is_visted[width][width][max_dir]={0};”,初始化为0,表示64个方格具有的出口方向(最多有八个)未被访问,两个数组同样是全局变量。[/b]

骑士前进函数:

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()[b]循环。但是,[b]使用“1” 与“0”分别表示完成游历与回到原点,即while()循环进行下去的条件无论是0还是1都不符合。解决办法是将是否完成游历和是否回到原点分成2个功能函数:is_sucess(),1代表完成游历,0代表还未完成游历;int
is_back_to_start(),1代表回到起始原点,0代表没有回到其实原点。那么,while()循环进行下去的条件就是:![b]is_end()&&
!is_back_to_start(),既没有完成游历也没有回到原点。[/b][/b][/b]

判定骑士是否回到原点

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]),检查半天也没找到错误。最后,用了近三个小时[b]调试程序才找到这个错误。完整代码我已经上传到我的资源了,下载就行,使用的编程软件是VS2010。[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: