首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

小弟我整理的文章:跳马有关问题深化。最后提出了一个有关问题,请大家解决

2012-02-15 
我整理的文章:跳马问题深化。最后提出了一个问题,请大家解决。【问题描述】跳马问题也称骑士遍历、马踏棋盘问题

我整理的文章:跳马问题深化。最后提出了一个问题,请大家解决。
【问题描述】
跳马问题也称骑士遍历、马踏棋盘问题:在8*8方格的棋盘上,从任意指定的方格出发,为象棋中的马寻找一条走遍棋盘每一格并且只经过一次的一条路径。

【回溯算法】
程序可以这样来写:
#include   <stdio.h>
//全局变量
void   search(int   x)
{
//改变标志位,环境变量等等:这些改变就相当于完成一步动作,在跳马问题中就是马向前跳一步,在后面的八皇后问题中就相当于放置了一个皇后

//如果找到匹配的,则输出结果
//否则,找到当前步可能的路径,然后search(x+1),即进行下一步动作;这里可能有多个分支,如跳马问题中马可以往八个方向跳,在后面的八皇后问题中每个皇后可能有8个摆放位置,每个分支都要列举出来,以便回溯时使用

//回退:还原标志位,环境变量等等,即清除当前步的痕迹
}
void   main()
{
search(初始值);
}

下面是跳马问题的完整程序:

//马踏棋盘的递归程序
//棋盘大小:8*8
//起始位置:0,0
#define   M   8
#define   N   8
#include   <stdio.h>

int   map[M][N];
int   count=0;//记录马共跳的步数
int   countnum=0;//用于统计走法种数
int   direction[8][2]={{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2}};//马可能前进的8个方向

void   print_map();//打印出马跳的路径
void   go(int   i,int   j);//马向(i,j)点跳

void   main()
{
freopen( "out.txt ", "w ",stdout);
for(int   k=0;k <M;k++)
for(int   l=0;l <N;l++)
map[k][l]=0;
go(0,0);
printf( "End   at:%ld\n ",countnum);  
}

void   go(int   i,int   j)
{
//改变标志位,环境变量等等:这些改变就相当于完成一步动作,在跳马问题中就是马向前跳一步
count++;//计数器
map[i][j]=count;//棋盘置当前步数

//如果找到匹配的,则输出结果
if(count==64)//如果已经遍历棋盘则打印一次走法
{
print_map();
}
//否则,找到当前步可能的路径,然后search(x+1),即进行下一步动作;这里可能有多个分支,如跳马问题中马可以往八个方向跳,每个分支都要列举出来,以便回溯时使用
else
{
int   m,n;
for   (int   k=0;k <8;k++)
{
m=i+direction[k][0];
n=j+direction[k][1];
if(m> =0   &&   m <M   &&   n> =0   &&   n <N)
if(map[m][n]==0)
{
go(m,n);
}
}
}

//回退:还原标志位,环境变量等
map[i][j]=0;//回退,置零
count--;//计数器减

}

void   print_map()
{
for(int   i=0;i <M;i++)
{
for(int   j=0;j <N;j++)
printf( "%3d ",map[i][j]);
printf( "\n ");
};
countnum=countnum+1;
printf( "Now   Count:%d\n\n ",countnum);
}


【相关问题】
我原来有两个疑惑:
(1).回溯算法跟深度优先搜索算法的区别在哪里?上面用回溯法来解跳马问题,其实就是一种深度优先搜索的思想。
(2).8*8的跳马问题复杂度比较大,如果策略选择不好,很可能半天出不来结果。如上述程序从(1,1)起跳很快能找到解;但是如果从(3,3)起跳,很长时间也出不来结果。上述程序中,8个方向的先后顺序组合一个策略,如果把8个方向的先后顺序换一下,如先顺序是:(2,1)、(2,-1)、(1,2)、(1,-2)、(-2,1)、(-2,-1)、(-1,2)、(-1,-2),也从(1,1)起跳,但是很难得到结果。不知道8个方向策略的选择有什么窍门,我至今没有找出来。
经过研究,终于明白了一些。
对于第一个问题,实际上,几乎所有算法书上,都有专门一章专门介绍回溯算法,但是深度优先搜索,一般是当作一种解决特定问题时说的。也就是说,深度优先搜索是运用回溯算法的体现。
对于第二个问题,策略的选择是有规律的,这就是带有启发式的回溯算法,运用它可以对算法进行优化,加快搜索速度。

【启发式搜索】
对于跳马问题一个启发式规则是,在选择当前步的方向时去选择满足下面条件的方向,当按这个方向推进到下一位置时,这个位置所可以再选择的方向最少。也就是说在当前位置优先选一个走起来比较“艰难”的方向来推进。加入这种启发式规则之后,运行效果加快了很多,无论从哪个位置起跳,很快就能找到解。
那么,怎么在上述程序的基础上加入启发式思想呢?首先,引入一个难易矩阵,如下。它表示从每一个点出发,可以朝几个方向跳,换言之,也就是可以有多少方向能到达此点。方向越多,说明越容易跳到。
int   accessibility[M][N]=//       棋盘相应点的难易值
{{       2,3,4,4,4,4,3,2       },
    {       3,4,6,6,6,6,4,3       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,


    {       3,4,6,6,6,6,4,3       }       ,
    {       2,3,4,4,4,4,3,2       }       };
要让马先跳难跳的位置,就必须对每一个点的所有方向进行排序,排序后,就是当前点马的跳法策略。在此,可以用冒泡排序进行排序,把马不可能跳的方向也方向(即马会超出边界的方向)也排到后面,这样,马如果在(i,j)点,只要依次跳accessibility[i][j]个方向即可,而后面的方向是不可能跳到的。
加入启发式思想后,对上述程序改进如下。如果想找一条循环路径,即马跳到最后一步之后,再跳一步又回到了起点,只要把go函数里的print_map()上两行加入即可。运用此程序,很快就能找到解。
/*
对于骑士游历问题一个启发式规则是,在选择当前步的方向时去选择满足下面条件的方向,
当按这个方向推进到下一位置时,这个位置所可以再选择的方向最少。
也就是说在当前位置优先选一个走起来比较 "艰难 "的方向来推进。
*/
//马踏棋盘的递归程序
//棋盘大小:8*8
//起始位置:0,0
#define   M   8
#define   N   8
#include   <stdio.h>

int   map[M][N];
int   count=0;
int   countnum=0;     //用于统计走法种数
int   accessibility[M][N]=//       棋盘相应点的难易值
{{       2,3,4,4,4,4,3,2       },
    {       3,4,6,6,6,6,4,3       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       4,6,8,8,8,8,6,4       }       ,
    {       3,4,6,6,6,6,4,3       }       ,
    {       2,3,4,4,4,4,3,2       }       };

int   direction[8][2]={{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2}};//马可能前进的8个方向
int   newDirection[8][2];//排序后的方向

void   print_map();
bool   go(int   i,int   j);
void   sort(int   a,int   b);

void   main()
{
freopen( "out.txt ", "w ",stdout);
for(int   k=0;k <M;k++)
for(int   l=0;l <N;l++)
map[k][l]=0;
go(0,0);
printf( "End   at:%ld\n ",countnum);  
}

bool   go(int   i,int   j)
{
count++;//计数器
map[i][j]=count;//棋盘置当前步数
if(count==64)//如果已经遍历棋盘则打印一次走法
{
//for   (int   a=0;a <8;a++)
//if   ((i   +   direction[a][0])==0   &&   (j   +   direction[a][1])==0)
print_map();
}
else
{
int   m,n;
sort(i,j);
for   (int   k=0;k <accessibility[i][j];k++)//实际上不是每个点都有8个方向,应该是accessibility[i][j]个方向
{
m=i+newDirection[k][0];
n=j+newDirection[k][1];
if(m> =0   &&   m <M   &&   n> =0   &&   n <N)
if(map[m][n]==0)
{
go(m,n);
sort(i,j);
}
}
//sort(i,j);
}

map[i][j]=0;//回退,置零
count--;//计数器减

return   false;//注意:如果函数没有返回值,会默认返回true
}

void   sort(int   a,int   b)//对(a,b)点的8个方向进行重新排序,把较难走的排在前面
{
int   i,j,swap_x,swap_y;
for   (i=0;i <8;i++)
{
newDirection[i][0]   =   direction[i][0];
newDirection[i][1]   =   direction[i][1];
}
//冒泡排序:把马越容易跳着的点的方向放到后面;另外,也把不可能的方向放到后面
for   (i=8-1;i> =0;i--)
for   (j=0;j <i;j++)
{
if(!(a+newDirection[j+1][0]   <   0   ||   a+newDirection[j+1][0]   > =   M   ||   b+newDirection[j+1][1]   <   0   ||   b+newDirection[j+1][1]   > =   N))


{
if(a+newDirection[j][0]   <   0   ||   a+newDirection[j][0]   > =   M   ||   b+newDirection[j][1]   <   0   ||   b+newDirection[j][1]   > =   N)
{
swap_x   =   newDirection[j][0];
swap_y   =   newDirection[j][1];
newDirection[j][0]   =   newDirection[j+1][0];
newDirection[j][1]   =   newDirection[j+1][1];
newDirection[j+1][0]   =   swap_x;
newDirection[j+1][1]   =   swap_y;
}
else   if(accessibility[a+newDirection[j][0]][b+newDirection[j][1]]   >   accessibility[a+newDirection[j+1][0]][b+newDirection[j+1][1]])
{
swap_x   =   newDirection[j][0];
swap_y   =   newDirection[j][1];
newDirection[j][0]   =   newDirection[j+1][0];
newDirection[j][1]   =   newDirection[j+1][1];
newDirection[j+1][0]   =   swap_x;
newDirection[j+1][1]   =   swap_y;
}
}
}
}

void   print_map()
{
for(int   i=0;i <M;i++)
{
for(int   j=0;j <N;j++)
printf( "%3d ",map[i][j]);
printf( "\n ");
};
countnum=countnum+1;
printf( "Now   Count:%d\n\n ",countnum);
}


【问题】
go函数里调用了两次sort函数,第一次是在给选择策略作准备,第二次是恢复原来的策略,为回溯作准备。
这里有一个问题:第二次sort必须紧接着go(m,n),而不能写在for循环结束之后,否则会打印出很多相同的路径。
这个问题我调试了很久,也没有找出原因。

完整的文章:http://longmang.blog.edu.cn/user2/longmang/archives/2007/1648764.shtml
文章下载:http://longmang.blog.edu.cn/UploadFiles/2007-2/224155834.rar

请大家帮忙解决问题呀。

[解决办法]
不是你自己说每次go()调用都回改变newdire数组嘛 当然要go()返回要马上改 要不然再次循环时根据上次调用的ij排序了
[解决办法]
不要光写代码 写点想法大家才好讨论

热点排行