回溯算法

回溯算法

引言
      在<<算法系列---回溯算法>>一节,讨论回溯算法及其应用,回溯能够在可以接受的时间内解决某些规模的组合问题,这节再讨论它的一个非常有意思的应用---跳马问题(骑士周游问题)。

问题
跳马问题也称为骑士周游问题,是算法设计中的经典问题。其一般的问题描述是:

     考虑国际象棋棋盘上某个位置的一只马,它是否可能只走63步,正好走过除起点外的其他63个位置各一次?如果有一种这样的走法,则称所走的这条路线为一条马的周游路线。试设计一个算法找出这样一条马的周游路线。

此题实际上是一个Hamilton回路问题,和迷宫问题很相似,可以用回溯算法来解决.
考虑到马在每一个位置,最多有8种跳法,如下图所示:
 

        
  

K7

 

K0

   
 

K6

   

K1

  
   

K

    
 

K5

   

K2

  
  

K4

 

K3

   
        
        

可以使用N皇后问题的算法模板。
算法如下:

#include <stdio.h>
#include <stdlib.h>
/**//**/
//棋盘行数
const int N = 8;

int step[N * N] = {-1}; //保存每一步做出的选择
int chess[N][N] = {0}; //棋盘
//下一个方向
int Jump[8][2] = {{-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}};

int p = 0;//对解的个数计数

//判断是否合法
int canJump(int x, int y)
{
    if (x >= 0 && x < N && y >= 0 && y < N && chess[x][y] == 0)
        return 1;
    return 0;
}
//输出结果
void OutPutChess()
{
    int i;
    for (i = 1; i <= N * N - 1; ++i)
        {
            printf("%d ", step[i]);
        }
    printf("/n");
    for(i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
            printf("%3d ",chess[i][j]);
        printf("/n");
    }
}

//回溯算法
void BackTrace(int t, int x, int y)
{
    if (t >= N * N)
    //if(t>=37)
    {
        p++;
        OutPutChess();//输出结果
        exit(1);   //求得一个解时,退出程序
        //return;  //求得所有解
    }
    else
    {
        for (int i = 0; i < 8; ++i)
        {
            if (canJump(x + Jump[i][0], y + Jump[i][1]))
            {   //求下一个结点
                x += Jump[i][0];
                y += Jump[i][1];

                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];
            }
        }
       
    }
}
/**//**/
int main()
{
    int x = 0;
    int y = 0;
    chess[x][y] = 1;
    BackTrace(1, x, y);
    printf("All Results Number = %d ", p);
}

该算法最坏的时间复杂度为O(8N*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>
/**//**/
//棋盘行数
const int N = 8;
int step[N * N] = {-1}; //保存每一步做出的选择
int chess[N][N] = {0}; //棋盘
int Jump[8][2] = {{-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}};
int p = 0;//对解的个数计数
//判断是否合法
int canJump(int x, int y)
{
    if (x >= 0 && x < N && y >= 0 && y < N && chess[x][y] == 0)
        return 1;
    return 0;
}
//求权值
int weightStep(int x, int y)
{
    int count = 0;
    for (int i = 0; i < 8; ++i)
    {
        if (canJump(x + Jump[i][0], y + Jump[i][1]))
            count++;
    }
    return count;
}
//权值排序
void inssort(int a[], int b[], int n)
{
    if (n <= 0) return;
    for (int i = 0; i < n; ++i)
    {
        for (int j = i; j > 0; --j)
        {
            if (a[j] < a[j - 1])
            {
                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()
{
    int i;
    for (i = 1; i <= N * N - 1; ++i)
        {
            printf("%d ", step[i]);
        }
    printf("/n");
    for(i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
            printf("%3d ",chess[i][j]);
        printf("/n");
    }
}
//回溯算法
void BackTrace(int t, int x, int y)
{
    if (t >= N * N)
    {
        p++;
        OutPutChess();//输出结果
        exit(1);   //求得一个解时,退出程序
        //return;  //求得所有解
    }
    else
    {
        int i;
        int count[8], possibleSteps[8];
        int k = 0;
        for (i = 0; i < 8; ++i)
        {
            if (canJump(x + Jump[i][0], y + Jump[i][1]))
            {
                count[k] = weightStep(x + Jump[i][0], y + Jump[i][1]); //求权值
                possibleSteps[k++] = i;   //保存下一个结点的序号
            }
        }
       
        inssort(count, possibleSteps, k);//排序
        for (i = 0; i < k; ++i)
        {
            int d = possibleSteps[i];
            //跳向下一个结点
            x += Jump[d][0];
            y += Jump[d][1];
            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];
           
        }
    }
}
int main()
{
   
    int x = 0;
    int y = 0;
    chess[x][y] = 1;
    BackTrace(1, x, y);
    printf("All Results Number = %d ", p);
}

如果如下:
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值