!-- 基于C语言的迷宫求解问题 --!

迷宫求解问题


    我们要先初始化一个迷宫地图,这里分为两种,一类为简单迷宫(不带环),一类为复杂迷宫(带环)。具体示例如下,这里采用6*6的数组表示迷宫地图,每个位置上对应元素的值非0即1,1表示路,0表示墙;
    这里迷宫求解问题,我们可以采用两种方式求解,一种是利用函数本身递归的调用,一种则是利用循环一直查找。且这里的迷宫求解问题也分为两类,一类是简单的查找迷宫的一条路径,一类则是找到迷宫的最小路径。

一.  查找迷宫的一条通路

    1. 我们从入点开始,先判断入口点是否能走,若入口点都不能走,说明传入参数错误,直接返回;
    2. 入口点能走,我们就将其标记,并放入栈中;
    3. 检查当前点是否为出口(我们将迷宫四周的非入口点都可以看做出口),是出口就找到了一条路;
    4. 若当前点不是出口,则对当前点按顺时针方向(上、右、下、左)的顺序依次探测,是否能走,能走就标记入栈,再按同样的方式继续向下探测,直至找到出口;
    5. 直至找到一条完整的路径时,查找结束,栈内存放即为路径。

二. 查找迷宫的最短路径

    1. 定义两个栈,一个为cur当前栈,存放查找的路径,一个为short栈,存放最短路径;
    2. 我们从入点开始,先判断入口点是否能走,若入口点都不能走,说明传入参数错误,直接返回;
    3. 入口点能走,我们就将其标记,并放入cur栈中;
    4. 检查当前点是否为出口(我们将迷宫四周的非入口点都可以看做出口),是出口就找到了一条路;
   5. 若当前点不是出口,则对当前点按顺时针方向(上、右、下、左)的顺序依次探测,是否能走,能走就标记入栈,再按同样的方式继续向下探测,直至找到出口;
    6. 找到一条路时, 若cur栈中路径比short栈内路径短,或是short栈为空,则用cur中路径替代short中路径;
    7. 直至再也找不到新的路径了,此时short栈内即为最短路径。


代码如下,其中也有必要的解释:

maze.h
#pragma once

#include <stdio.h>
#include <stdlib.h>

#define SHOW_NAME printf("\n===================%s=================\n",__FUNCTION__);
#define ROW 6
#define COL 6

typedef int MazeType;
typedef struct Point
{
    MazeType row;
    MazeType col;
}Point;

typedef struct Maze
{
    MazeType map[ROW][COL];
}Maze;

typedef struct SeqStack
{
    Point* data;
    size_t size;
    size_t capacity;
}SeqStack;


maze.c
#include "maze.h"

void PointInit(Point* entry)
{
    if(entry == NULL)
        return;
    entry->row = 0;
    entry->col = 1;
    return;
}

void MazeInit(Maze* maze)//初始化,建立一个迷宫地图
{
    if(maze == NULL)
        return;
    int map[ROW][COL] = {{0,1,0,0,0,0},\
                         {0,1,1,1,0,0},\
                         {0,1,0,1,1,1},\
                         {1,1,1,0,0,0},\
                         {0,0,1,0,0,0},\
                         {0,0,1,0,0,0}};//1表示路,0表示墙,之后走过的路标记为2
    size_t i = 0;
    for(; i<ROW; ++i)
    {   
        size_t j = 0;
        for(; j<COL; ++j)
        {   
            maze->map[i][j] = map[i][j];
        }   
    }   
    return;
}

void MazePrint(Maze* maze, const char* msg)//打印迷宫
{
    printf("[%s]\n",msg);
    if(maze == NULL)
        return;
    size_t i = 0;
    for(; i<ROW; ++i)
    {
        size_t j = 0;
        for(; j<COL; ++j)
        {
            printf("%2d ",maze->map[i][j]);
        }
        printf("\n");
    }
    return;
}

int CanStay(Maze* maze, Point cur)//判断当前点是否能落脚
{
    if(maze == NULL)
        return 0;
    if(cur.row < 0 || cur.row >= ROW || cur.col < 0 || cur.col >= COL)//当前点越界,不在迷宫地图内
        return 0;
    if(maze->map[cur.row][cur.col] == 1)
        return 1;
    return 0;
}

void Mark(Maze* maze, Point cur)//标记当前点,将对应值改为2即可
{
    if(maze == NULL)
        return;
    maze->map[cur.row][cur.col] = 2;
    return;
}

int IsExit(Maze* maze, Point cur, Point entry)//判断当前点是否为出口,注意判断是否为入口
{
    if(maze == NULL)
        return 0;
    if(cur.row == entry.row && cur.col == entry.col)//当前点为入口点
        return 0;
    if(cur.row == 0 || cur.row == ROW-1 || cur.col == 0 || cur.col == COL-1)//迷宫有四条边,每一个边上的点都可作出口
        return 1;
    return 0;
}

///////////////////////////////////////////////////////////////////
////1.利用递归查找简单迷宫的一条路径
///////////////////////////////////////////////////////////////////

void _GetPath(Maze* maze, Point cur, Point entry)//真正实现查找迷宫路径的函数
{
    printf("cur : (%d, %d)\n",cur.row,cur.col);//可看到函数调用的过程
    if(maze == NULL)
        return;
    //1.判断当前点是否能落脚
    if(!CanStay(maze,cur))
        return;
    //2.能落脚就标记
    Mark(maze,cur);
    //3.判断当前点是否为出口(非入口、迷宫最外围),为出口,就找到一条路径
    if(IsExit(maze,cur,entry))
    {
        printf("找到一条路\n");
        return;
    }
    //4.不为出口,顺时针探测四周点,递归调用函数本身,递归时更新cur
    Point up = cur;//上方点
    up.row -= 1;
    _GetPath(maze,up,entry);
    Point right = cur;//右方点
    right.col += 1;
    _GetPath(maze,right,entry);
    Point down = cur;//下方点
    down.row += 1;
    _GetPath(maze,down,entry);
    Point left = cur;//左方点
    left.col -= 1;
    _GetPath(maze,left,entry);
    return;
}

void GetPath(Maze* maze,Point entry)//寻找迷宫路径
{
    if(maze == NULL)
        return;
    _GetPath(maze,entry,entry);//调用寻找函数,第二个参数表示当前位置,第三个参数表示入口点
}

///////////////////////////////////////////////////////////////////
////2.利用非递归(循环)查找简单迷宫的一条路径
///////////////////////////////////////////////////////////////////

void SeqStackInit(SeqStack* stack)
{
    if(stack == NULL)
        return;
    stack->size = 0;
    stack->capacity = 1000;
    stack->data = (Point*)malloc(sizeof(Point) * stack->capacity);
    return;
}

void SeqStackPush(SeqStack* stack,Point value)
{
    if(stack == NULL)
        return;
    if(stack->size >= stack->capacity)
        return;
    stack->data[(stack->size)++] = value;
    return;
}

void SeqStackPop(SeqStack* stack)
{
    if(stack == NULL)
        return;
    if(stack->size == 0)
        return;
    --stack->size;
    return;
}

int SeqStackGetTop(SeqStack* stack, Point* value)
{
    if(stack == NULL || value == NULL)
        return 0;
    if(stack->size == 0)
        return 0;
    *value = stack->data[stack->size - 1];
    return 1;
}

void SeqStackPrint(SeqStack* stack, const char* msg)
{
    printf("[%s]\n",msg);
    if(stack == NULL)
        return;
    size_t i = 0;
    for(; i<stack->size; ++i)
    {
        printf("(%d, %d)\n",stack->data[i].row,stack->data[i].col);
    }
    printf("\n");
}
void GetPathByLoop(Maze* maze, Point entry)
{
    if(maze == NULL)
        return;
    //1.创建一个栈,用于保存走过的路径
    SeqStack stack;
    SeqStackInit(&stack);
    //2.判断入口能否落脚(不能说明传入的参数非法)
    if(!CanStay(maze,entry))
        return;
    //3.能落脚就标记并入栈
    Mark(maze,entry);
    SeqStackPush(&stack, entry);
    //4.进入循环,获取当前栈顶元素
    while(1)
    {
        Point top;
        int ret = SeqStackGetTop(&stack, &top);
        if(ret == 0)//栈为空,回溯结束
            return;
        //5.判断是否为出口
        if(IsExit(maze,top,entry))
        {
            printf("找到一条路\n");
            SeqStackPrint(&stack,"找到的路径为");
            return;
        }
        //6.顺时针取四周点,判断是否能落脚。能落脚就标记且入栈,进行下一轮循环
        Point up = top;//上方点
        up.row -= 1;
        if(CanStay(maze,up))
        {
            Mark(maze,up);
            SeqStackPush(&stack,up);
            continue;
        }
        Point right = top;//右方点
        right.col += 1;
        if(CanStay(maze,right))
        {
            Mark(maze,right);
            SeqStackPush(&stack,right);
            continue;
        }
        Point down = top;//下方点
        down.row += 1;
        if(CanStay(maze,down))
        {
            Mark(maze,down);
            SeqStackPush(&stack,down);
            continue;
        }
        Point left = top;//左方点
        left.col -= 1;
        if(CanStay(maze,left))
        {
            Mark(maze,left);
            SeqStackPush(&stack,left);
            continue;
        }
        //7.若四周都不能落脚,出栈顶元素(出到栈为空,则说明没路可走)
        SeqStackPop(&stack);
    }
}

///////////////////////////////////////////////////////////////////
////3.查找简单迷宫的最短路径(递归实现)
///////////////////////////////////////////////////////////////////


void SeqStackAssgin(SeqStack* from, SeqStack* to)
{//to的内存用来拷贝from的,有好几种可能性,这里为了保证代码的简洁性,统一先释放内存再重新申请再拷贝数据
    if(from==NULL || to==NULL)
        return;
    //1.释放to的原有内存
    free(to->data);
    to->size = 0;
    to->capacity = 0;
    //2.依据from的大小给to申请内存
    to->size = from->size;
    to->capacity = from->capacity;
    to->data = (Point*)malloc(sizeof(Point) * to->capacity);
    //3.数据拷贝
    size_t i = 0;
    for(; i<from->size; ++i)
    {
        to->data[i] = from->data[i];
    }
    return;
}

void _GetShortPath(Maze* maze, Point cur, Point entry, SeqStack* cur_path, SeqStack* short_path)
{
    //1.判断当前点是否能落脚
    if(!CanStay(maze,cur))
        return;
    //2.能落脚,标记并入cur_path栈
    Mark(maze,cur);
    SeqStackPush(cur_path,cur);
    //3.判断当前点是否为出口点
    if(IsExit(maze,cur,entry))
    {
        printf("找到了一条路\n");
        //1)为出口,找到一条路,此时若cur_path比short_path栈中路径短或short_path为空,用当前路径代替short_path中的路;
        if(cur_path->size < short_path->size || short_path->size==0)
        {
            printf("找到一条较短路径\n");
            SeqStackAssgin(cur_path, short_path);
        }
        //2)为出口,但比short_path中路径长,再回溯去找另外的路径,并出栈顶元素
        //这里不论cur_path中路径比short_path中短还是长,只要是出口,都要回溯,去找是否还有其他路径
        SeqStackPop(cur_path);
        return;
    }
    //4.不是出口,探测周围四个相邻点
    Point up = cur;
    up.row -= 1;
    _GetShortPath(maze,up,entry,cur_path,short_path);
    Point right = cur;
    right.col += 1;
    _GetShortPath(maze,right,entry,cur_path,short_path);
    Point down = cur;
    down.row += 1;
    _GetShortPath(maze,down,entry,cur_path,short_path);
    Point left = cur;
    left.col -= 1;
    _GetShortPath(maze,left,entry,cur_path,short_path);
    //5.四个点都探测过,就出栈回溯
    SeqStackPop(cur_path);
    return;
}

void GetShortPath(Maze* maze, Point entry)
{//先遍历找到所有路径,找到里面最短的即可
    SeqStack cur_path;
    SeqStack short_path;
    SeqStackInit(&cur_path);
    SeqStackInit(&short_path);
    _GetShortPath(maze, entry, entry, &cur_path, &short_path);
    SeqStackPrint(&short_path,"最短路径");
}

///////////////////////////////////////////////////////////////////
////4.复杂迷宫中查找最短路径(递归实现)
//////      复杂迷宫不仅有多个入口,路径上还可能带环      /////////
///////////////////////////////////////////////////////////////////

void ComplexMazeInit(Maze* maze)//初始化复杂迷宫地图
{
    if(maze == NULL)
        return;
    int map[ROW][COL] = {{0,1,0,0,0,0},\
                         {0,1,1,1,0,0},\
                         {0,1,0,1,1,1},\
                         {1,1,1,1,0,0},\
                         {0,0,1,0,0,0},\
                         {0,0,1,0,0,0}};//1表示路,0表示墙
    size_t i = 0;
    for(; i<ROW; ++i)
    {
        size_t j = 0;
        for(; j<COL; ++j)
        {
            maze->map[i][j] = map[i][j];
        }
    }
    return;
}

void PointPreInit(Point* pre)//入口点的前一个点初始化为(-1,-1)非法点
{
    if(pre == NULL)
        return;
    pre->row = -1;
    pre->col = -1;
    return;
}

int CanStayWithCycle(Maze* maze, Point pre, Point cur)
{
    //1.判断当前点是否在地图上
    if(cur.row < 0 || cur.row >= ROW || cur.col < 0 || cur.col >= COL)
        return 0;
    //2.当前点是否为墙(是则不能落脚)
    if(maze->map[cur.row][cur.col] == 0)
        return 0;
    //3.当前点是否为路(是则直接落脚)
    if(maze->map[cur.row][cur.col] == 1)
        return 1;
    //4.若当前点已走过,判断是否值得走
    //若此时走比之前走花的步数更少,就值得去走,若步数相同或多,就没有意义,因为我们要找最短路径
    //这里不用判断pre位置是否非法,因为仅当入口点为当前点时,pre才会非法,而入口点的值非0即1,前面已过滤
    int pre_value = maze->map[pre.row][pre.col];
    int cur_value = maze->map[cur.row][cur.col];
    if(pre_value + 1 < cur_value)
        return 1;
    return 0;
}

void MarkWithCycle(Maze* maze, Point pre, Point cur)
{//将走过的位置标记为入口点到该点的步数,入口点标记为2,实际步数为每个位置的值减去1
    if(pre.row==-1 && pre.col==-1)//当前点为入口点
    {
        maze->map[cur.row][cur.col] = 2;
        return;
    }
    int pre_value = maze->map[pre.row][pre.col];
    maze->map[cur.row][cur.col] = pre_value + 1;
    return;
}

void _GetShortPathWithCycle(Maze* maze, Point cur, Point pre, Point entry, SeqStack* cur_path, SeqStack* short_path)
{
    //1.判断当前点是否能落脚(与之前判断方法不同)
    if(!CanStayWithCycle(maze,pre,cur))
        return;
    //2.标记并入cur_path栈(与之前标记方法不同)
    MarkWithCycle(maze, pre, cur);
    SeqStackPush(cur_path, cur);
    //3.判断当前点是否为出口
    if(IsExit(maze, cur, entry))
    {
        //1)为出口,且为更短路径,替换short_path中的路径
        printf("找到一条路\n");
        if(cur_path->size < short_path->size || short_path->size == 0)
        {
            printf("找到一条较短路径\n");
            SeqStackAssgin(cur_path, short_path);
        }
        //2)只要当前点为出口,就要回溯去找其他路径
        SeqStackPop(cur_path);
        return;
    }
    //4.当前点不是出口,探测周围四个相邻点
   Point up = cur;
   up.row -= 1;
   _GetShortPathWithCycle(maze,up,cur,entry,cur_path,short_path);
   Point right = cur;
   right.col += 1;
   _GetShortPathWithCycle(maze,right,cur,entry,cur_path,short_path);
   Point down = cur;
   down.row += 1;
   _GetShortPathWithCycle(maze,down,cur,entry,cur_path,short_path);
   Point left = cur;
   left.col -= 1;
   _GetShortPathWithCycle(maze,left,cur,entry,cur_path,short_path);
    //5.四个点都探测完,出栈回溯
   SeqStackPop(cur_path);
   return;
}

void GetShortPathWithCycle(Maze* maze, Point pre, Point entry)
{//先遍历找到所有路径,找到里面最短的即可
    SeqStack cur_path;
    SeqStack short_path;
    SeqStackInit(&cur_path);
    SeqStackInit(&short_path);
    //该方法的标记与之前不同,还要用到前一步点,所以增加一个参数pre
    _GetShortPathWithCycle(maze, entry, pre, entry, &cur_path, &short_path);
    SeqStackPrint(&short_path,"最短路径");
}
///////////////////////////////////////////////////////////////////
////以下为测试代码,test1、2、3、4对应上面四种求解情况
///////////////////////////////////////////////////////////////////

void test1()
{
    SHOW_NAME;
    Maze maze;
    MazeInit(&maze);
    Point entry;
    PointInit(&entry);
    MazePrint(&maze,"初始迷宫地图");
    GetPath(&maze,entry);
    MazePrint(&maze,"寻找出路");
}

void test2()
{
    SHOW_NAME;
    Maze maze;
    MazeInit(&maze);
    Point entry;
    PointInit(&entry);
    MazePrint(&maze,"初始迷宫地图");
    GetPathByLoop(&maze,entry);
    MazePrint(&maze,"寻找出路");
}

void test3()
{
    SHOW_NAME;
    Maze maze;
    MazeInit(&maze);
    Point entry;
    PointInit(&entry);
    MazePrint(&maze,"初始迷宫地图");
    GetShortPath(&maze,entry);
}

void test4()
{
    SHOW_NAME;
    Maze maze;
    ComplexMazeInit(&maze);
    Point entry;
    Point pre;
    PointInit(&entry);
    PointPreInit(&pre);
    MazePrint(&maze,"初始迷宫地图");
    GetShortPathWithCycle(&maze, pre, entry);
}

int main()
{
    test1();
    test2();
    test3();
    test4();
    return 0;
}

运行结果如下:
1. 简单迷宫求解一条通路(递归实现)

此方法通过递归实现,探测路径所走过的点都保存在函数栈帧中,故这里未能将完整的路径所经过的点打印出来。

2. 简单迷宫求解一条通路(循环实现)
3. 简单迷宫求解最短路径

4. 复杂迷宫求解最短路径

猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80032760