• 算法系列回溯算法(续)


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

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

         考虑国际象棋棋盘上某个位置的一只马,它是否可能只走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= {{-21}{-12}{12}{21}{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= {{-21}{-12}{12}{21}{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 <= 0return;
        
    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);
    }


    如果如下:


  • 相关阅读:
    账户与安全
    VIM 文档编辑
    ubuntu下搭建Discuz
    数据库管理及增删改查基本操作小结
    poj 3320 jessica's Reading PJroblem 尺取法 -map和set的使用
    poj 3579 Median 二分套二分 或 二分加尺取
    poj 3685 Matrix 二分套二分 经典题型
    POJ 3061  Subsequence   尺取法   挑战146页
    poj 2976 Dropping tests 二分搜索+精度处理
    Codeforces Round #325 (Div. 2) A. Alena's Schedule 暴力枚举 字符串
  • 原文地址:https://www.cnblogs.com/hustcat/p/1151379.html
Copyright © 2020-2023  润新知