马踏棋盘算法(骑士周游问题)

一、马踏棋盘算法

1、国际象棋的棋盘为8*8的方格棋盘,将“马”放在任意指定的方格中,按照“马”走棋的规则将“马”进行移动。要求每个方格只能进入一次,最终使得“马”走遍棋盘64个方格。

2、关于国际象棋“马”的走法:以“日”字为单位往斜对角上的那个方格走。如下图所示马在当前位置就有8种走法,如果不考虑边缘的特殊情况,那么需要8^64的时间复杂度才可以把整个棋盘的一个正确路径找出来。


3、对于在n*n的棋盘上,当n>=5且为偶数的时候,以任意点作点都有解。

4、实现代码如下:

/************************************************************/
/**               马踏棋盘算法(骑士周游问题)                  **/
/************************************************************/

#include <stdio.h>
#include <time.h>

#define X 8
#define Y 8

int chess[X][Y];

/**
 * 打印棋盘,棋盘中每个格子的数值就是遍历的次序
 */
void Print()
{
    int i, j;
    for(i = 0; i < X; i++)
    {
        for(j = 0; j < Y; j++)
        {
            printf("%2d\t", chess[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

/**
 * 找到基于马当前在棋盘中的位置的(x,y)坐标的下一个可走的位置的坐标,如果成功找到则返回1,且直接修改原来位置的坐标;
 * 否则返回0
 * 成功找到的条件是该位置存在且还没有被马走过
 * @param x:当前马所在棋盘位置的x坐标
 * @param y:当前马所在棋盘位置的y坐标
 * @param count:不考虑边缘的情况,马在任意一位置的下一个位置都可能有八种情况,count就是对这八种情况进行判断
                 只要找到其中一种就返回1
 */
int NextXY(int *x, int *y, int count)
{
    switch(count)
    {
    case 1:
        if(*x + 2 <= X - 1 && *y - 1 >= 0 && chess[*x + 2][*y - 1] == 0)
        {
            *x += 2;
            *y -= 1;
            return 1;
        }
        break;
    case 2:
        if(*x + 2 <= X - 1 && *y + 1 <= Y - 1 && chess[*x + 2][*y + 1] == 0)
        {
            *x += 2;
            *y += 1;
            return 1;
        }
        break;
    case 3:
        if(*x + 1 <= X - 1 && *y - 2 >= 0 && chess[*x + 1][*y - 2] == 0)
        {
            *x += 1;
            *y -= 2;
            return 1;
        }
        break;
    case 4:
        if(*x + 1 <= X - 1 && *y + 2 <= Y - 1 && chess[*x + 1][*y + 2] == 0)
        {
            *x += 1;
            *y += 2;
            return 1;
        }
        break;
    case 5:
        if(*x - 2 >= 0 && *y - 1 >= 0 && chess[*x - 2][*y - 1] == 0)
        {
            *x -= 2;
            *y -= 1;
            return 1;
        }
        break;
    case 6:
        if(*x - 2 >= 0 && *y + 1 <= Y - 1 && chess[*x - 2][*y + 1] == 0)
        {
            *x -= 2;
            *y += 1;
            return 1;
        }
        break;
    case 7:
        if(*x - 1 >= 0 && *y - 2 >= 0 && chess[*x - 1][*y - 2] == 0)
        {
            *x -= 1;
            *y -= 2;
            return 1;
        }
        break;
    case 8:
        if(*x - 1 >= 0 && *y + 2 <= Y - 1 && chess[*x - 1][*y + 2] == 0)
        {
            *x -= 1;
            *y += 2;
            return 1;
        }
        break;
    default:
        break;
    }
    return 0;
}

/**
 * 深度优先遍历棋盘
 * @param x:当前马即将要走的位置的x坐标
 * @param y:当前马即将要走的位置的y坐标
 * @param step:当前马即将要走的这个位置是第step步,也就是说马已经走过了step-1个棋盘
 */
int TraversalChessBoard(int x, int y, int step)
{
    // 定义x1和y1变量保存马即将要走的这个位置的坐标
    int x1 = x, y1 = y;
    // flag保存是否有下一个可走的位置;count用于循环判断八种可走的位置
    int flag = 0, count = 1;

    // 标记该位置已经走过了
    chess[x][y] = step;
    // 如果此时step等于棋盘格子的个数,则说明已经把棋盘的每一个格子都走过一次了
    // 则打印棋盘输出走过的顺序;这也是下面的递归的返回条件吧
    if(step == X * Y)
    {
        Print();
        return 1;
    }

    // 如果还没有走完棋盘,则选择下一个位置,依次判断那八种可能的情况
    flag = NextXY(&x1, &y1, count);
    while(flag == 0 && count < 8)
    {
        count++;
        flag = NextXY(&x1, &y1, count);
    }

    // 在当前位置找到下一个可走的位置
    while(flag)
    {
        // 递归调用走向下一个位置,因为在NextXY函数中直接修改了当前位置的坐标,所以此时的x1和y1就表示下一个可走位置的坐标
        // 如果此时返回的是1,说明棋盘已经走完了,继续向上返回1
        if(TraversalChessBoard(x1, y1, step + 1))
        {
            return 1;
        }
        // 返回的不是1,则返回上一个位置,在上一个位置重新选择一个可走位置继续
        x1 = x;
        y1 = y;
        // 前count种可走位置的情况都判断过了,从count+1种情况继续判断
        count++;
        flag = NextXY(&x1, &y1, count);
        while(flag == 0 && count < 8)
        {
            count++;
            flag = NextXY(&x1, &y1, count);
        }
    }

    // 八种可能的情况都判断过了,还是没有下一个可走的位置了
    // 那么说明该次深度遍历失败,此种走法不行,则应该从当前位置回退到上一个位置
    // 所以重新标记当前位置还没有被走过
    if(0 == flag)
    {
        chess[x][y] = 0;
    }

    // 如果都没有可走的位置了,则返回0,代表继续向后回退
    return 0;
}

int main()
{
    int i, j;
    clock_t start, finish;
    start = clock();

    for(i = 0; i < X; i++)
    {
        for(j = 0; j < Y; j++)
        {
            chess[i][j] = 0;   // 初始化棋盘
        }
    }

    // 从坐标(2,0)开始走
    if(!TraversalChessBoard(2, 0, 1))
    {
        printf("遍历棋盘失败......\n");
    }
    finish = clock();
    printf("本次遍历共耗时:%f秒\n", (double)(finish - start)/CLOCKS_PER_SEC);
    return 0;
}
5、从实现代码上来看,该算法运用到了递归法、回溯法以及图的深度遍历思想,不断的递归判断,失败再回退,直到成功遍历或者失败为止。不过实现方式还有很大的可优化空间。运行结果如下,需要化很长一段时间来运行,不过起始点的选择很重要,有些时候选择了其他的起始点,则有可能很久很久都计算不出来。


6、其他相关概念

哈密尔顿路径:图G中的哈密尔顿路径指的是经过图G中每个顶点,且只经过一次的一条轨迹。如果这条轨迹是一条闭合的路径(从起点出发不重复地遍历所有点后仍能回到起始点),那么这条路径称为哈密尔顿回路。没有闭合的叫通路或路径。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值