C实现迷宫问题

迷宫问题分析
用二维码创建一个m*n的迷宫地图,1表示通路,0表示障碍,从迷宫中寻找出路。

迷宫问题大致有三种情况实现:

  1. 使用递归和非递归方法实现简单的迷宫问题
  2. 如果迷宫有多条出路,求最短出路
  3. 针对复杂迷宫问题寻找最短出路

下面对其依次进行分析:

1、使用递归和非递归方法实现简单的迷宫问题
这里的简单迷宫问题就是只有一条出路,且出路不带环,实现它的主要思路就是应用回溯的思想,他的具体步骤可以总结如下:
1)、判定当前点是否能落脚
2)、给当前位置做标记(若能落脚)
3)、若当前点为出口,就找到一条出路,探测结束
4)、若当前点不是出口,就按照顺时针探测四个相邻的点,递归的调用函数自身,递归时,更新cur点(每次递归时,点都是下一次要走的点,这个点究竟能不能走,交给递归函数作判断)

而使用递归和非递归两种方法的主要区别主要在与,使用递归时,操作系统会自动给其维护一个栈,而不需要我们手动维护,而使用非递归,就需要自己创建一个栈,用来存放出路各点。

2、如果迷宫有多条出路,求最短出路

当迷宫有多条的出路的时候,并且要找到其最短的出路,那么我们就需要维护两个栈,一个用于保存找到的出路,另外一个用于保存目前最短的那个出路,每次找到一条出路的时候,都跟目前最短的那个出路进行比较,如果比其短,就保存在用于保存最短出路的栈里,当找完所有出路后,然后返回第二个栈,就找到了最短出路

3、针对复杂迷宫问题寻找最短出路

复杂迷宫问题就是有多条出路,且出路带环,需要解决出路带环问题,和简单迷宫问题的区别就在于,判定是否能落脚和标记规则改变,其他的思路都是大致相同的。
判定是否能够落脚规则:
1)、当前点是否在地图上
2)、当前点是不是墙
3)、如果当前点是1,就说明可以落脚
4)、当前点如果走过,比较cur对应的值和pre对应的值的大小关系,在取出pre_value之前,要判定pre点是否是一个非法点(cur和pre分别是当前点和前一个点的坐标)
标记规则不再是将已经做过的路标记为2了,而是标记为pre+1

代码实现如下:

//maze.c
#include <stdio.h> 
#include "maze.h"
#include "SqStack.h"

void MazeInit(Maze *maze){
    int map[MAX_ROW][MAX_COL] = {
    {0,1,0,0,0,0},
    {0,1,0,0,0,0},
    {0,1,1,1,0,0},
    {0,1,0,1,0,0},
    {0,1,0,0,0,0},
    {0,1,0,0,0,0}};
    size_t i = 0;
    for(; i < MAX_ROW; i++){
        size_t j = 0;
        for(; j < MAX_COL; j++){
            maze -> map[i][j] = map[i][j];
        }
    }
}

void MazePrint(Maze *maze){
    size_t i = 0;
    for(; i < MAX_ROW; i++){
        size_t j = 0;
        for(; j < MAX_COL; j++){
            printf("%2d", maze -> map[i][j]);
        }
        printf("\n");
    }
}
///////////////////////////////////////////////
/////Round1 使用递归的方式来解决迷宫的问题
//////////////////////////////////////////////

void Mark(Maze *maze, Point cur){
    maze -> map[cur.ROW][cur.COL] = 2;
}


int IsExit(Maze *maze, Point cur, Point entry){
    (void)maze;
        //1、当前点不是入口,如果是入口,说明肯定不是出口
        //2、如果当前点在地图边界上,也说明是出口
    if(cur.ROW == entry.ROW && cur.COL == entry.COL){
        return 0;
    }
    if(cur.ROW == 0 || cur.COL == MAX_ROW - 1 || cur.COL == 0 || cur.COL == MAX_COL - 1){
        return 1;
    }
    return 0;
}
//判定pt这个点是否能够落脚
//如果能落脚返回1,否则返回0
int CanStay(Maze *maze, Point pt){
    //1、若pt在地图外,不能落脚
    //2、pt在地图内,若这个位置为1能落脚,若为0,2,不能让落脚
    if(pt.ROW < 0 || pt.ROW >= MAX_ROW || pt.COL < 0 || pt.COL >= MAX_COL){
        return 0;
    } 
    int value = maze -> map[pt.ROW][pt.COL];
    if(value == 1){
        return 1;
    }
    return 0;
}

void _GetPath(Maze *maze, Point cur, Point entry){
    printf("cur:%d,%d\n", cur.ROW, cur.COL);
    //1、判定当前点是否能落脚
    //2、给当前位置做标记(若能落脚)
    //3、若当前点为出口,就找到一条出路,探测结束
    //4、若当前点不是出口,就按照顺时针探测四个相邻的点,递归的调用函数自身,递归时,更新cur点(每次递归时,点都是下一次要走的点,这个点究竟能不能走,交给递归函数作判断)
    if(!CanStay(maze, cur)){
        return;
    }
    Mark(maze, cur);
    if(IsExit(maze, cur, entry)){
        printf("找到一条出路!\n");
        return;
    }
    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);
}

void GetPath(Maze *maze, Point entry){
    if(maze == NULL){
        return;//非法输入
    }
    //辅助递归 
    _GetPath(maze,entry,entry);
}
////////////////////////////////////////////////////////////////
////Round2 使用非递归的方式来解决迷宫问题
///////////////////////////////////////////////////////////////
void GetPathByLoop(Maze *maze, Point entry){
    //1、创建一个栈,并且初始化,这个栈保存着当前路径
    SqStack stack;
    InitSqStack(&stack);
    //2、判断入口点能不能落脚,如果不能,说明参数非法
    if(!CanStay(maze, entry)){
        return;//入口点非法
    }
    //3、标记入口点,并且将入口点入栈
    Mark(maze, entry);
    SqStackPush(&stack, entry);
    //4、进入循环,获取当前的栈顶元素(栈顶元素一定能落脚)
    while(1){
        Point cur;
        int ret = SqStackTop(&stack, &cur);
        if(ret == 0){
            return;//栈为空说明回溯结束
        }

    //5、判定这个当前点是否能落脚
    //6、能落脚就进行标记
    //7、判定这个点是不是出口,如果是出口直接函数返回
        if(IsExit(maze, cur, entry)){
            printf("找到一条出路\n");
            return;
        }
    //8、按照顺时针方向取相邻点,判定相邻点是否能落脚,如果能落脚,就标记并且入栈,立刻进入下一轮循环
    Point up = cur;
    up.ROW -= 1;
    if(CanStay(maze, up)){
        Mark(maze, up);                                                                                                                                                                        
        SqStackPush(&stack, up);
        continue;
    }
    Point right = cur;
    right.COL += 1;
    if(CanStay(maze, right)){
        Mark(maze, right);
        SqStackPush(&stack, right);
        continue;
    }
    Point down = cur;
    down.ROW += 1;
    if(CanStay(maze, down)){
        Mark(maze, down);
        SqStackPush(&stack, down);
        continue;
    }
    Point left = cur;
    left.COL -= 1;
    if(CanStay(maze, left)){
        Mark(maze, left);
        SqStackPush(&stack, left);
        continue;
    }
    //9、如果四个相邻的点都不能落脚,就出栈当前点,相当于进行回溯
    SqStackPop(&stack);
    }
    return;
}

//////////////////////////////////////////////////////////////////
//Round3 如果迷宫有多条路径,找到其中最短的路径
/////////////////////////////////////////////////////////////////
void MazeInitShortPath(Maze *maze){
    int map[MAX_ROW][MAX_COL] = {
        {0,1,0,0,0,0},
        {0,1,1,1,0,0},
        {0,1,0,1,1,1},
        {0,1,0,0,0,0},
        {1,1,0,0,0,0},
        {0,0,0,0,0,0}
    };
    size_t i = 0;
    for(; i < MAX_ROW; i++){
        size_t j = 0;
        for(; j < MAX_COL; j++){
            maze -> map[i][j] = map[i][j];
        }
    }
    return;
}
void _GetShortPath(Maze *maze, Point cur, Point entry, SqStack *cur_path, SqStack *short_path){
    //1、判定当前点是否能落脚
    if(!CanStay(maze, cur)){
        return ;
    }
    //2、如果能落脚,就对当前点进行标记,同时把当前点插入到cur_path
    Mark(maze, cur);
    SqStackPush(cur_path, cur);
    //3、判定当前点是否是出口
    if(IsExit(maze, cur, entry)){
        printf("找到一条路径!\n");
        if(cur_path -> size <  short_path -> size || short_path -> size == 0){                                                                                                                 
            printf("找到了一条比较短的路径!\n");
            SqStackAssgin(cur_path, short_path);
        }else{
    //a)如果当前点是出口,说明当前找到一条路径,就拿当前路径和short_path中的路径进行对比,如果当前路径比short_path短,或者short_path本身是一个
    //空栈,就用当前路径代替short_path,代替完毕之后,也是需要回溯的,尝试找其他路劲
    //b)如果当前路径没有比short_path短就要尝试去找其他路径(进行回溯)在回溯之前,也要把cur_path栈顶元素也出栈
        SqStackPop(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、如果四个方向都递归的探测过了,就可以进行出栈(指当前函数栈帧结束,同时cur_path也要进行出栈),回溯到上一个点
    SqStackPop(cur_path);
    return;
}

//递归版本
void GetShortPath(Maze *maze, Point entry){
    //保存当前找到的路径
    SqStack cur_path;
    //保存最短路径
    SqStack short_path;
    InitSqStack(&cur_path);
    InitSqStack(&short_path);                                                                                                                                                                  
    _GetShortPath(maze, entry, entry, &cur_path, &short_path);
    //打印栈中的内容
    SqStackDebugPrint(&short_path, "最短路径是");
}
/////////////////////////////////////////////////////////////////
//Round4 针对复杂的迷宫地图找出其中的最短路径,这里的复杂不关指迷宫有
//多个出口,同时路径上肯能带环
/////////////////////////////////////////////////////////////////
void MazeInitShortPathWithCycle(Maze *maze){
    int map[MAX_ROW][MAX_COL] = {
        {0,1,0,0,0,0},
        {0,1,1,1,0,0},
        {0,1,0,1,1,1},
        {0,1,1,1,0,0},
        {1,1,0,0,0,0},
        {0,0,0,0,0,0}
    };
    size_t i = 0;
    for(; i < MAX_ROW; i++){
        size_t j = 0;
        for(; j < MAX_COL; j++){
            maze -> map[i][j] = map[i][j];
        }
    }
    return;
}
//判定是否能落脚
int CanStayWithCycle(Maze *maze, Point cur, Point pre){
    //1、当前点是否在地图上
    if(cur.ROW < 0 || cur.ROW >= MAX_ROW || cur.COL < 0 || cur.COL >= MAX_COL){
        return 0;
    }
    //2、当前点是不是墙
    int cur_value = maze -> map[cur.ROW][cur.COL];
    if(cur_value == 0){
        return 0;
    }                                                                                                                                                                                          
     //3、如果当前点是1,这是可以直接落脚 
    if(cur_value == 1){
        return 1;
    }
    //4、当前点如果已经走过了,比较cur对应的值和pre对应的值的大小关系,在取出pre_value之前,要判定pre点是否是一个非法点
    //cur_value > pre_value + 1,就应该落脚
    //if(pre.ROW == -1 && pre.COL == -1){
        //pre如果是非法点,就只有一种情况
        //但是这种情况又不需要考虑,因为如果pre为非法点,说明cur就是入口点,判定入口点是否能落脚,和pre没关系,直接判定cur_value是0,还是1就行,而这样的逻辑,已经在刚刚判定过了
    //}
    int pre_value = maze -> map[pre.ROW][pre.COL];
    if(cur_value > pre_value + 1){
        return 1;
    }
    return 0;

}

void MarkWithCycle(Maze *maze, Point cur, Point pre){
    if(pre.ROW == -1 && pre.COL == -1){
        //针对入口点进行标记,此时的pre点是非法点
        //不能根据pre_value + 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;
}
void _GetShortPathWithCycle(Maze *maze, Point cur, Point pre, Point entry, SqStack *cur_path, SqStack *short_path){
    //1、判定当前点是否能落脚(判定规则变了)
    if(!CanStayWithCycle(maze, cur, pre)){
        return;
    }
    //2、标记当前前点(标记规则也变了),并且当前点插入到cur_path中
    MarkWithCycle(maze, cur, pre);
    SqStackPush(cur_path, cur);
    //3、判定当前点是否为出口 
    if(IsExit(maze, cur, entry)){
        printf("找到一条路径!\n");
        if(cur_path -> size < short_path -> size || short_path -> size == 0){
            printf("找到了一条比较短的路径!\n");
            SqStackAssgin(cur_path, short_path);
        }
        SqStackPop(cur_path);
        return;
    }
    //如果是出口,要拿cur_path和short_path进行对比,把比较短的路径保存在short_path中
    //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、如果四个方向都探测过,就出栈回溯
    SqStackPop(cur_path);
    return;
}

//实现一个递归版本
void GetShortPathWithCycle(Maze *maze, Point entry){
    SqStack cur_path;
    SqStack short_path;
    InitSqStack(&cur_path);
    InitSqStack(&short_path);
    Point pre = {-1, -1};
    _GetShortPathWithCycle(maze, entry, pre, entry, &cur_path, &short_path);                                                                                                                   
    SqStackDebugPrint(&short_path, "最短路径为");
}

//////////////////////////////////////////////////////////////////
//////////////////////////////TEST///////////////////////////////
////////////////////////////////////////////////////////////////
#if 1
#include <stdio.h>

#define TESTHEADER printf("######################%s#####################\n",__FUNCTION__)


void TestRound1(){
    TESTHEADER;
    Maze maze;
    MazeInit(&maze);
// MazePrint(&maze);
    Point entry = {0, 1};
    GetPath(&maze, entry);
    MazePrint(&maze);
    //MazePrint(&maze);
}
void TestRound2(){
    TESTHEADER;
    Maze maze;
    MazeInit(&maze);
    Point entry = {0, 1};
    GetPathByLoop(&maze, entry);
    MazePrint(&maze);        
}
void TestRound3(){
    TESTHEADER;
    Maze maze;
    MazeInitShortPath(&maze);
    Point entry = {0, 1};
    GetShortPath(&maze, entry);
    MazePrint(&maze);                                                                                                                                                                          
}
void TestRound4(){
    TESTHEADER;
    Maze maze;
    MazeInitShortPathWithCycle(&maze);
    Point entry = {0, 1};
    GetShortPathWithCycle(&maze, entry);
    MazePrint(&maze);
}
int main(){
    TestRound1();
    TestRound2();
    TestRound3();
    TestRound4();
}
#endif 

//maze.h 

#pragma once 

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



#define MAX_ROW 6
#define MAX_COL 6

typedef struct Maze{
    int map[MAX_ROW][MAX_COL];
}Maze;

//SqStack.h
#pragma once 
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>



#define FOR_MAZE
#ifdef FOR_MAZE

//#define FOR_MAZE
typedef struct Point{
    int ROW;
    int COL;
}Point;
typedef Point SqStackType;

#else
typedef char SqStackType;
#endif

typedef struct SqStack{
    SqStackType* data;
    size_t size;
    size_t capacity; //MAXSIZE的替代品,data这段内存中能容纳的元素个数
}SqStack;

void InitSqStack(SqStack *sq);//初始化栈

void SqStackPush(SqStack *sq, SqStackType value);//进栈

void SqStackPop(SqStack *sq);
//返回两个信息:执行成功失败;栈顶元素的值是多少
int SqStackTop(SqStack *sq, SqStackType* value);

void SqStackDestroy(SqStack *sq);

void SqStackAssgin(SqStack *from, SqStack *to);

void SqStackDebugPrint(SqStack *sq, const char *msg);

//SqStack.c
#include <stdio.h> 
#include "SqStack.h"

void InitSqStack(SqStack *sq){
    if(sq == NULL){
        return;//非法输入
    }
    sq -> size = 0;
    sq -> capacity = 1000;
    sq -> data = (SqStackType*)malloc(sq -> capacity *sizeof(SqStackType));
}
//栈的扩容策略
void SqStackReSize(SqStack *sq){
    if(sq == NULL){
        return;
    }
    if(sq -> size < sq -> capacity){
        return;
    }
    sq -> capacity = sq -> capacity * 2 + 1;//capacity*2(是C++里面的一个方法) + 1(为了方式capacity为0)
    SqStackType* new_ptr = (SqStackType*) malloc(sq -> capacity *sizeof(SqStackType));
    size_t i = 0;
    for(; i < sq -> size; ++i){
    new_ptr[i] = sq -> data[i];
    }
    free(sq -> data);
    sq -> data = new_ptr;
}
//入栈
void SqStackPush(SqStack *sq, SqStackType value){
    if(sq == NULL){
        return;//非法输入

    }
    if(sq -> size >= sq -> capacity){//栈满进行扩容
        SqStackReSize(sq);
        return;
    }
    sq -> data[sq -> size++] = value;
    }

//出栈
void SqStackPop(SqStack *sq){
    if(sq == NULL){
     return; 
    }
    if(sq -> size == 0){
        return;
    }
    --sq -> size;
    }

//取栈顶元素,成功返回1,失败返回0
int SqStackTop(SqStack *sq, SqStackType* value){
    if(sq == NULL){
        return 0;//非法输入
    }
    if(sq -> size == 0){
        return 0;//栈为
    }
    *value = sq -> data[sq -> size -1];
    return 1;
}
//销毁栈
void SqStackDestroy(SqStack* sq){
    if(sq == NULL){
        return;//非法输入

    }
    free(sq -> data );
    sq -> size = 0;                                                                                                                                                                            
    sq -> capacity = 0;
}
void SqStackAssgin(SqStack *from, SqStack *to){
    //为了保证to里面的内存能够足够的容纳from中的元素
    //就采用以下策略:
    //1、释放to中的原有内存
    SqStackDestroy(to);
    //2、根据from元素的个数,确定内存申请大小,再给to重新申请一个足够的内存
    to -> size = from -> size;
    to -> capacity = from -> capacity;
    to -> data = (SqStackType*)malloc(to -> capacity * sizeof(SqStackType));
    //3、进行数据拷贝
    size_t i = 0;
    for(; i < from -> size; ++i){
        to -> data[i] = from -> data[i];
    }
}


//此函数仅用于迷宫问题中,用于调试,通常意义下,栈是不允许遍历的
//但是如果是进行调试或者测试,这个是例外
//因此在这里的函数虽然进行了调试,但是仅用于调试
//为了能够从入口到出口的顺序来打印栈中的内容
void SqStackDebugPrint(SqStack *sq, const char *msg){
    printf("[%s]\n", msg);
    size_t i = 0;
    for(; i < sq -> size; ++i){
        printf("(%d, %d)\n",sq -> data[i].ROW, sq -> data[i].COL);
    }
    printf("\n");
}



    原文作者:迷宫问题
    原文地址: https://blog.csdn.net/erwuhuoche/article/details/80193660
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞