美文网首页
马走日问题的解决--贪心算法

马走日问题的解决--贪心算法

作者: iDucky131 | 来源:发表于2019-04-06 19:46 被阅读0次

问题描述:

在8×8方格(国际象棋)的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条路径

问题分析:

首先这是一个搜索问题,运用深度优先搜索进行求解是完全可行的,它输入的是全部解,但是马遍历当8×8时解是非常之多的,用天文数字形容也不为过,这样一来求解的过程就非常慢,并且出一个解也非常慢。
怎么才能快速地得到部分解呢?

其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择‘出口’最小的进行搜索,如:
马当前的位置在(i,j)只有三个出口,它们的位置是(i+2,j+1)、(i-2,j+1)和(i-1,j-2),如分别走到这三个位置,这三个位置又分别会有不同的出口,假定这三个位置的出口个数分别为4、2、3,则程序就选择让走向出口个数最少的(i-2,j+1)位置。
该过程没有回溯,但对于某些开始位置实际上有解,而该算法不能找到解,对于这种情况,只要改变八种可能出口的选择顺序就能找到解

为什么要这样选取?

这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现‘死’结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。这种算法称为为贪心算法。

算法思想步骤概括为下面的几点:
①找到一个位置的各个方向的出口(OUT1)。
②对各个方向的出口进行二次出口(OUT2)的搜索。
③记录每个方向上出口(OUT1)的二次出口(OUT2),(OUT2)最小的的那个(OUT1)即为接下来要访问的位置。
④将每次访问过的位置放入容器。
⑤如果不能够正常找到,则回退一步,改变出口顺序。
代码如下(C/C++):

#include <stdlib.h>
#define N 8 //棋盘的规模
#define M 8 //棋子有8个方向可以走

/*采用贪心算法
 *每一次选择的时候选择当前节点可走方向中出口最少的那一个
 *这样走的步数越多,剩下的节点中可走的方向越多,能够遍历所有点的可能性越大
 *但是这存在一个问题,就是可能这个点本来是可以走过所有点的,但是由于选择
 *导致没有走出,这时只要遍历8个方向就一定能够走出来
*/

int VISITED[N][N];//记录已经走过的点

/*
*node结构体:path中每个元素的结构
*x:int,记录横坐标
*y:int,记录纵坐标
*dir:int,记录处于该点时行走的方向
*/
struct node
{
    int x;
    int y;
    int dir;
};
/*
*stack结构体:记录行走路径的结构体
*point:int,栈顶指针
*/
struct Stack
{
    node path[N * N];
    int point;
};
/*
*Dir:记录行走的的坐标变化单位量
*x:int,x的变化量
*y:int,y的变化量
*/
struct Dir
{
    int x;
    int y;
};

//记录每个点八个方向中哪些可以走完全部的点,默认全部都可以
//主要用于回退
int nodeCanDir[N][N][M];

Dir direction[M] = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}}; //记录某个方向的坐标变化值

/*
*eBound:检查坐标是否越界
*input: 
* x:int,横坐标
* y:int,纵坐标
*return:
*bool,越界返回false,否则返回true
*/
bool eBound(int x, int y)
{
    if (x < 0 || x >= N)
    {
        return false;
    }
    else if (y < 0 || y >= N)
    {
        return false;
    }
    return true;
}

/*
*cDir:算出某点可走的方向数
*input: 
* x:int,横坐标
* y:int,纵坐标
*return:
*int,该点可走的方向数
*/
int cDire(int x, int y)
{
    int number = 0;
    int temp_x;
    int temp_y;
    for (int i = 0; i < M; i++)
    {   //按照i方向更新坐标
        temp_x = x + direction[i].x;
        temp_y = y + direction[i].y;
        //判断这个方向是否可走,可走的话number加1
        if (eBound(temp_x, temp_y) && VISITED[temp_x][temp_y] == 0)
        {
            number++;
        }
    }
    return number;
}

/*
*selDir:选择行走方向(这个方向是a的话,那么走到a后可行走的方向数最少)
*input: 
* x:int,横坐标
* y:int,纵坐标
*return:
*dire:行走方向
*int,所选方向拥有的可走方向数
*/
int selDir(int x, int y, int *dire)
{
    int minDir = 0;     //可走方向最少的方向
    int minNum = M + 1; //可走方向最少的那个方向有多少可以走的
    int temp;
    int temp_x;
    int temp_y;
    for (int i = 0; i < M; i++)
    {
        temp_x = x + direction[i].x;
        temp_y = y + direction[i].y;
        //如果这个方向是出口,走到这个出口时下一步有多少个出口
        if (eBound(temp_x, temp_y) && nodeCanDir[x][y][i] == 0 && VISITED[temp_x][temp_y] == 0)
        {
            temp = cDire(x + direction[i].x, y + direction[i].y);
            //更新具有最小出口点的坐标
            minDir = (minNum >= temp) ? i : minDir;
            minNum = (minNum >= temp) ? temp : minNum;
        }
    }
    *dire = minDir;
    return minNum;
}
/*
*push:进栈函数
* x:int,横坐标
* y:int,纵坐标
* dire:行走方向
*/
void push(Stack *p, int x, int y, int dire)
{
    int point = p->point;
    p->path[point].x = x;
    p->path[point].y = y;
    p->path[point].dir = dire;
    p->point++;
}
/*
*pop:出栈函数
*p:栈顶指针
*f:存储出栈点信息
*return:
*是否出栈成功
*/
bool pop(Stack *p,node *f){
    //如果栈空,则无法出栈
    if(p->point==0){
        return false;
    }
    else{
        f->x=p->path[p->point].x;
        f->y=p->path[p->point].y;
        f->dir=p->path[p->point].dir;
        p->point--;
        return true;
    }
}
/*
*jump:行走函数
*p:栈顶指针
* x:int,横坐标
* y:int,纵坐标
*/
bool jump(Stack *p, int x, int y)
{
    if (p->point == N * N)
    {
        return true;
    }
    else
    {
        int minDir;
        int minNum;
        minNum = selDir(x, y, &minDir);
        //如果selDir过程中没有任何可走的方向
        if (minNum == M + 1)
        {
            node *f=(node*)malloc(sizeof(node));
            //如果是最后一个点
            if (p->point == N * N - 1)
            {
                push(p, x, y, minDir);
                free(f);
                return true;
            }//出栈后没有空,则回溯
            else if(pop(p,f)){
                x=f->x;
                y=f->y;
                VISITED[x][y]=0;
                nodeCanDir[x][y][f->dir]=1;//此路不通
                free(f);
                return jump(p,x,y);
            }else{
                printf("Sorry! Path don't exist.");
                free(f);
                return false;
            }
        }
        else
        {   //如果是正常找到一个方向,将当前节点进栈,继续递归寻找
            //将当前点入栈
            push(p, x, y, minDir);
            VISITED[x][y] = 1;
            //更新点坐标
            x = x + direction[minDir].x;
            y = y + direction[minDir].y;
            //继续下一步
            return jump(p, x, y);
        }
    }
}

int main()
{
    Stack *p = (Stack *)malloc(sizeof(Stack));
    p->point = 0; //堆栈初始化
    printf("Please input start position between (0,0) and (8,8):\n");
    int startX = 2;
    int startY = 5;
    printf("x:");
    scanf("%d",&startX);
    printf("y:");
    scanf("%d",&startY);
    if (jump(p, startX, startY))
    {
        for (int i = 0; i < p->point; i++)
        {
            printf("(%d,%d)--", p->path[i].x, p->path[i].y);
            if(i!=0&&i%6==0){
                printf("\n");
            }
        }
    }
    printf("\n");
    free(p);
    system("pause");
    return 0;
}

结果:


result

参考文章:
主要参考了这篇文章关于回溯的处理
用C语言解决棋盘上马遍历问题
和这篇文章对贪心算法用于马踏棋盘的讲解
马踏棋盘(马的遍历问题)

相关文章

  • 马走日问题的解决--贪心算法

    问题描述: 在8×8方格(国际象棋)的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条路...

  • 【算法打卡60天】Day29贪心算法:如何用贪心算法实现Huff

    Day29学习内容 :贪心算法:如何用贪心算法实现Huffman压缩编码? 1.如何理解贪心算法?贪心算法解决问题...

  • 可用贪心算法解决的几个基本问题

    可用贪心算法解决的几个基本问题 关键:看问题有没有贪心选择性质和最优子结构性质。有些问题看似是可以用贪心算法,但是...

  • 算法之贪心

    昨天是刚接触贪心算法吧, 贪心应该算是比较常用的算法, 但是在实际的题目中却很少能解决问题, 只能解决固定的贪心类...

  • 【数据结构】贪心算法和动态规划

    动态规划和贪心算法都是用来求最优化问题,且二者都必须具有最有子结构。贪心算法可以解决的问题,动态规划都能解决,可以...

  • 14《算法入门教程》贪心算法之背包问题

    1. 前言 本节内容是贪心算法系列之一:背包问题,主要讲解了什么是背包问题,如何利用贪心算法解决背包问题,给出了背...

  • 13《算法入门教程》贪心算法之活动选择问题

    1. 前言 本节内容是贪心算法系列之一:活动选择问题,主要讲解了什么是活动选择问题,如何利用贪心算法解决活动选择问...

  • 【算法】贪心算法(0-1背包问题)

    什么是贪心算法? 贪心算法并不是一个具体的算法,而是一种算法的思想,或者说是解决问题一种思路。这就有两个关键的点,...

  • 第三十七节-贪心算法

    贪心算法解决问题的步骤 当我们看到这类问题的时候,首先要联想到贪心算法:针对一组数据,我们定义了它的限制值和期望值...

  • 贪心算法

    贪心算法解决问题的步骤 第一步,当我们看到这类问题的时候,首先要联想到贪心算法:针对一组数据,我们定义了限制值和期...

网友评论

      本文标题:马走日问题的解决--贪心算法

      本文链接:https://www.haomeiwen.com/subject/ywcdiqtx.html