引言
在<<算法系列---回溯算法>>一节,讨论回溯算法及其应用,回溯能够在可以接受的时间内解决某些规模的组合问题,这节再讨论它的一个非常有意思的应用---跳马问题(骑士周游问题)。
问题
跳马问题也称为骑士周游问题,是算法设计中的经典问题。其一般的问题描述是:
考虑国际象棋棋盘上某个位置的一只马,它是否可能只走63步,正好走过除起点外的其他63个位置各一次?如果有一种这样的走法,则称所走的这条路线为一条马的周游路线。试设计一个算法找出这样一条马的周游路线。
此题实际上是一个Hamilton回路问题,和迷宫问题很相似,可以用回溯算法来解决.
考虑到马在每一个位置,最多有8种跳法,如下图所示:
| | | | | | | |
| | K7 | | K0 | | | |
| K6 | | | | K1 | | |
| | | K | | | | |
| K5 | | | | K2 | | |
| | K4 | | K3 | | | |
| | | | | | | |
| | | | | | | |
可以使用N皇后问题的算法模板。
算法如下:
#include
<
stdio.h
>
#include
<
stdlib.h
>
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
/**/
/**/
//
棋盘行数
const
int
N
=
8
;
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
step[N
*
N]
=
{-1}
;
//
保存每一步做出的选择
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
chess[N][N]
=
{0}
;
//
棋盘
//
下一个方向
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
Jump[
8
][
2
]
=
{
{-2, 1},
{-1, 2},
{1, 2},
{2, 1},
{2, -1},
{1, -2},
{-1, -2},
{-2, -1}}
;
![](/Images/OutliningIndicators/None.gif)
int
p
=
0
;
//
对解的个数计数
![](/Images/OutliningIndicators/None.gif)
//
判断是否合法
int
canJump(
int
x,
int
y)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (x >= 0 && x < N && y >= 0 && y < N && chess[x][y] == 0)
return 1;
return 0;
}
//
输出结果
void
OutPutChess()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int i;
for (i = 1; i <= N * N - 1; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
printf("%d ", step[i]);
}
printf("\n");
for(i=0;i<N;i++)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
for(int j=0;j<N;j++)
printf("%3d ",chess[i][j]);
printf("\n");
}
}
![](/Images/OutliningIndicators/None.gif)
//
回溯算法
void
BackTrace(
int
t,
int
x,
int
y)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (t >= N * N)
//if(t>=37)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
p++;
OutPutChess();//输出结果
exit(1); //求得一个解时,退出程序
//return; //求得所有解
}
else
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
for (int i = 0; i < 8; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (canJump(x + Jump[i][0], y + Jump[i][1]))
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ //求下一个结点
x += Jump[i][0];
y += Jump[i][1];
![](/Images/OutliningIndicators/InBlock.gif)
printf("(%2d,%2d)",x,y);//打印当结点
chess[x][y] = t + 1;
step[t] = i;
BackTrace(t + 1, x, y);//递归调用
//回溯
chess[x][y] = 0;
x -= Jump[i][0];
y -= Jump[i][1];
}
}
}
}
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
/**/
/**/
int
main()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int x = 0;
int y = 0;
chess[x][y] = 1;
BackTrace(1, x, y);
printf("All Results Number = %d ", p);
}
该算法最坏的时间复杂度为O(8
N*N)。这是一个相当大的数字,不能接受,但实际情况好得多。并且在37步的时候,开
始
发生回溯,可通过改BackTrace中的第一个if中的参数发现。
据说,总的回溯次数达300多百万次.
我的机子运行20分钟也没运行出结果.我们可以考虑,当N=8时,为2192,即使对于2100=1.3*1030,对于一台每秒1万亿(1012)次操作的计算机,也需要4*1010才能完成,超过了45亿年---地球的估计年龄.
但是,该算法可以适当改进,考虑到:
即向前看两步,当每准备跳一步时,设准备跳到(x, y)点,计算(x, y)这一点可能往几个方向跳(即向前看两步),将这个数目设为(x, y)点的权值,将所 有可能的(x, y)按权值排序,从最小的开始,循环遍历所有可能的(x, y),回溯求出结果。算法可以求出所有可能的马跳棋盘路径,算出一个可行 的结果很快,但在要算出所有可能结果时,仍然很慢,因为最坏时间复杂度本质上并没有改变,仍为O(8^(N * N)),但实际情况很好,在瞬间即可得到一个解,当然,要求得所有解,也需要很长的时间.
下面是实现这一思想的代码:
#include
<
stdio.h
>
#include
<
stdlib.h
>
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
/**/
/**/
//
棋盘行数
const
int
N
=
8
;
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
step[N
*
N]
=
{-1}
;
//
保存每一步做出的选择
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
chess[N][N]
=
{0}
;
//
棋盘
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
int
Jump[
8
][
2
]
=
{
{-2, 1},
{-1, 2},
{1, 2},
{2, 1},
{2, -1},
{1, -2},
{-1, -2},
{-2, -1}}
;
![](/Images/OutliningIndicators/None.gif)
int
p
=
0
;
//
对解的个数计数
![](/Images/OutliningIndicators/None.gif)
//
判断是否合法
int
canJump(
int
x,
int
y)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (x >= 0 && x < N && y >= 0 && y < N && chess[x][y] == 0)
return 1;
return 0;
}
//
求权值
int
weightStep(
int
x,
int
y)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int count = 0;
for (int i = 0; i < 8; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (canJump(x + Jump[i][0], y + Jump[i][1]))
count++;
}
return count;
}
//
权值排序
void
inssort(
int
a[],
int
b[],
int
n)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (n <= 0) return;
for (int i = 0; i < n; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
for (int j = i; j > 0; --j)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (a[j] < a[j - 1])
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
int temp = a[j - 1];
a[j - 1] = a[j];
a[j] = temp;
temp = b[j - 1];
b[j - 1] = b[j];
b[j] = temp;
}
}
}
}
//
输出结果
void
OutPutChess()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int i;
for (i = 1; i <= N * N - 1; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
printf("%d ", step[i]);
}
printf("\n");
for(i=0;i<N;i++)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
for(int j=0;j<N;j++)
printf("%3d ",chess[i][j]);
printf("\n");
}
}
//
回溯算法
void
BackTrace(
int
t,
int
x,
int
y)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
if (t >= N * N)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
p++;
OutPutChess();//输出结果
exit(1); //求得一个解时,退出程序
//return; //求得所有解
}
else
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
int i;
int count[8], possibleSteps[8];
int k = 0;
for (i = 0; i < 8; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (canJump(x + Jump[i][0], y + Jump[i][1]))
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
count[k] = weightStep(x + Jump[i][0], y + Jump[i][1]); //求权值
possibleSteps[k++] = i; //保存下一个结点的序号
}
}
inssort(count, possibleSteps, k);//排序
![](/Images/OutliningIndicators/InBlock.gif)
for (i = 0; i < k; ++i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
int d = possibleSteps[i];
//跳向下一个结点
x += Jump[d][0];
y += Jump[d][1];
![](/Images/OutliningIndicators/InBlock.gif)
chess[x][y] = t + 1;
step[t] = d;
BackTrace(t + 1, x, y); //递归调用
//回溯
chess[x][y] = 0;
x -= Jump[d][0];
y -= Jump[d][1];
}
}
}
![](/Images/OutliningIndicators/None.gif)
int
main()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int x = 0;
int y = 0;
chess[x][y] = 1;
BackTrace(1, x, y);
printf("All Results Number = %d ", p);
}
如果如下: