数据结构-回溯法解决迷宫问题

回溯法:对一个包括有很多个结点,每个结点有若干个搜索分支的问题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止。

迷宫问题

首先我们需要有一张地图:
这里写图片描述
用0来标记此路不通,用1来标记此路可以走,后面会用2来标记走过的路。
我们默认出口和入口是在边界上的点,(出口与入口不是同一个)。
我们将走过的路的坐标压入栈中,如果能继续向下走,就一直入栈,直到走到出口。
如果下一个点不通,就将该点出栈。以此类推。
已上图为例,画一个栈来表示一下过程:
大致的出栈入栈过程如下图所示,灰色框表示该点由于不通或是已经被标记而已经被出栈。
这里写图片描述

递归实现

第一种方法,借助于函数递归调用时的栈帧结构,依赖于地址空间中的栈来辅助完成回溯法。
首先定义数据结构。

#include <stddef.h>
#define ROW 6
#define COL 6
typedef struct Maze
{
    size_t maze[ROW][COL];
}Maze;

typedef struct Point
{
    int x;
    int y;
}Point;

接下来,对地图初始化并且打印出来。

void MazeInit(Maze* m)
{
    if(m==NULL)
    {
        return;
    }
    int arr[ROW][COL]={
        {0,0,1,0,0,0},
        {0,0,1,0,0,0},
        {0,0,1,0,0,0},
        {0,0,1,1,1,0},
        {0,0,1,0,1,1},
        {0,0,0,0,0,0}
    };
    int i=0;
    int j=0;
    for(i=0;i<ROW;i++)
    {
        for(j=0;j<COL;j++)
        {
            m->maze[i][j]=arr[i][j];
        }
    }
}
void MazePrint(Maze* m)
{
    if(m==NULL)
    {
        return;
    }
    int i=0;
    int j=0;
    for(i=0;i<ROW;i++)
    {
        for(j=0;j<COL;j++)
        {
            printf("%d ",m->maze[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

这里写图片描述

然后,需要搜索路径:

void _GetPath(Maze* m,Point entry,Point cur)
{
    printf("cur:(%d,%d)\n",cur.x,cur.y);
    //每次走到下一个点,都要调用此函数
    //1.判断当前点能否能落脚
    if(!CanStay(m,cur))
    {
        return;
    }
    //2.若能落脚,给当前位置坐标记
    Mark(m,cur);
    //3.若当前点为出口,说明找到了一条出路,探测就结束
    if(Exit(m,cur,entry))
    {
        printf("找到了一条出路\n");
        return;
    }
    //4。若当前点不是出口,按顺时针顺序探测四个方向的相邻点

    Point up=cur;
    up.x-=1;
    _GetPath(m,entry,up);

    Point right=cur;
    right.y+=1;
    _GetPath(m,entry,right);

    Point down=cur;
    down.x+=1;
    _GetPath(m,entry,down);

    Point left=cur;
    left.y-=1;
    _GetPath(m,entry,left);
}

void GetPath(Maze* m,Point entry)
{
    if(m==NULL)
    {
        return;
    }
    _GetPath(m,entry,entry);
}

辅助判断函数如下:

void Mark(Maze* m,Point cur)
{
    //走过的地方标记为2
    m->maze[cur.x][cur.y]=2;

}
int Exit(Maze* m,Point pt,Point entry)
{
    (void)m;
    //1当前点是不是入口,若为入口,就不是出口
    if(pt.x==entry.x&&pt.y==entry.y)
    {
        return 0;
    }
    //2.如果当前点在边界上,就是出口
    if(pt.x==0||pt.y==0||pt.x== ROW-1||pt.y==COL-1)
    {
        return 1;
    }
    return 0;
}
int CanStay(Maze* m,Point pt)
{
    //判断pt该点是否能落脚
    //若该点在地图外,则不能落脚
    if(pt.x<0||pt.x>=ROW||pt.y<0||pt.y>=COL)
    {
        return 0;
    }
    //若该点在地图内,位置为1,就能落脚,为0或2都不能落脚
    int value=m->maze[pt.x][pt.y];
    if(value==1)
    {
        return 1;
    }
    return 0;
}

结果如下:
将每次压入栈的点坐标也打印出来。
这里写图片描述
可以看到再找到出路之后,还是会回溯的遍历,直到将所有能走的点都标记过,才停止。

下图可以看到,确实将每个能走的点都标记了。
这里写代码片

手动定义一个栈实现

与上述思路是一样的,只是这里我们不采用函数调用时的栈帧,而是自己手动维护一个栈结构。
对于该栈结构,入栈表示保存当前点的坐标。出栈表示该点不能走了,必须要进行回溯。
代码如下:代码中有详细的注释

void GetPathByLoop(Maze* m,Point entry)
{
    //1.创建一个栈,并且初始化
    SeqStack stack;
    SeqStackInit(&stack);
    //2.判定入口能不能落脚,如果不能,说明参数非法
    if(!CanStay(m,entry))
    {
        return;
    }
    //3.标记入口点,将入口点入栈
    Mark(m,entry);
    SeqStackPush(&stack,entry);
    //4.进入循环,获取到当前的栈顶元素(栈顶元素一定能落脚)
    Point cur;
    while(SeqStackTop(&stack,&cur))
    {
        //5.判定这个点是不是出口,如果是出口,直接函数返回。
        //6.按照顺时针方向取相邻点,判定相邻点是否能够落脚,如果能落脚就标记入栈,立刻进入下一层循环。
        //7.如果四个相邻点都不能落脚,就出栈当前点回溯
        if(Exit(m,cur,entry))
        {
            printf("找到了一条路径\n");
            return;
        }
        Point up=cur;
        up.x-=1;
        if(CanStay(m,up))
        {
            Mark(m,up);
            SeqStackPush(&stack,up);
            continue;
        }

        Point right=cur;
        right.y+=1;
        if(CanStay(m,right))
        {
            Mark(m,right);
            SeqStackPush(&stack,right);
            continue;
        }

        Point down=cur;
        down.x+=1;
        if(CanStay(m,down))
        {
            Mark(m,down);
            SeqStackPush(&stack,down);
            continue;
        }

        Point left=cur;
        left.y-=1;
        if(CanStay(m,left))
        {
            Mark(m,left);
            SeqStackPush(&stack,left);
            continue;
        }

        SeqStackPop(&stack);
    }
}

结果如下所示:

这里写图片描述

阅读更多
个人分类: 数据结构
上一篇Linux-core dump详解
下一篇操作系统-理解线程概念
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭