栈的应用 -- 迷宫求解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_36629696/article/details/79976438

迷宫求解

0 0 1 0 0 0

0 0 1 1 1 0

1 1 1 0 1 1

0 0 1 0 1 0

0 0 1 1 1 1

0 0 0 0 0 0

如上图所示的一个迷宫, 0为墙, 1为路, 编写程序求解

1) 求简单迷宫是否存在路径
2) 求多出口迷宫的最短路径
3) 求带环的多出口迷宫的最短路径

思路: 利用栈保存走过的点, 走不通时开始回溯, 直到找到出口

求简单迷宫是否存在路径

1, 递归版本


//迷宫求解
//思路: 从一个入口点开始, 依次判断它的 上,右,下,左 方位能不能走 
//      如果能就直接走, 每走一步将这个位置的坐标入栈, 并且标记为2, 代表已经走过
//      若都不能走, 说明走到死路了, 要开始回溯
//      回溯时就是把入栈的坐标出栈, 即可原路返回, 每返回一步就重新再判断它的 上右下左 能不能走
//      直到找到出口
//方法一: 利用递归的函数调用栈, 保存已经走过的点
//方法二: 利用自己定义的栈, 保存已经走过的点
#include <stdio.h>

#define ROW 9
#define COL 9

//先定义一个迷宫地图
int map[ROW][COL] = {
    {0, 0, 1, 0, 0, 0, 1, 0, 0},
    {0, 0, 1, 0, 0, 0, 0, 0, 0},
    {0, 0, 1, 0, 0, 1, 1, 1, 0},
    {0, 0, 1, 0, 0, 1, 0, 0, 0},
    {0, 0, 1, 1, 1, 1, 0, 1, 0},
    {0, 0, 1, 0, 0, 1, 0, 1, 0},
    {0, 0, 1, 0, 0, 1, 1, 1, 1},
    {0, 0, 1, 0, 0, 1, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0},
};

//定义一个点
typedef struct point
{
    int row;
    int col;
}point;

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


//一, 利用递归的函数调用栈


//判断能否落脚的函数
int canStay(point cur)
{
    //点在地图外, 则不能走
    if(cur.row < 0 || cur.col < 0 || cur.row > ROW || cur.col > COL)
    {
        //点越界
        return 0;
    }

    //点的值是 0 或 2 则不能走
    if(map[cur.row][cur.col] == 0 || map[cur.row][cur.col] == 2)
    {
        return 0;
    }
    //点的值是 1 则能走
    if(map[cur.row][cur.col] == 1)
    {
        return 1;
    }
}

//遍历判断 上右下左 能否落脚的函数
//point CW_Traversal(point cur)
//{
//    //定义上右下左 4个点
//    point up = {cur.row-1, cur.col};
//    point right = {cur.row, cur.col+1};
//    point down = {cur.row+1, cur.col};
//    point left = {cur.row, cur.col-1};
//
//    if( canStay(up) )
//    {
//        return up;
//    }
//    if( canStay(right) )
//    {
//        return right;
//    }
//    if( canStay(down) )
//    {
//        return down;
//    }
//    if( canStay(left) )
//    {
//        return left;
//    }
//    //如果都能落脚, 就返回当前点
//    return cur;
//}

//标记
void Mark(point cur)
{
    map[cur.row][cur.col] = 2;
}

//判读出口
int isExit(point cur, point entry)
{
    //点的坐标在边界, 就说明是出口
    //但是如果是入口, 肯定不是出口
    if(cur.row == entry.row && cur.col == entry.col)
    {
        return 0;
    }
    else if(cur.row == 0 || cur.col == 0 || cur.row == ROW-1 || cur.col == COL-1)
    {
        return 1;
    }

    return 0;
}

//辅助递归
void assistRecursion(point now_cur, point entry)
{
    //判断这个入口点能否落脚
    if( canStay(now_cur) )
    {
        printf("cur : (%d, %d)\n", now_cur.row, now_cur.col);

        //如果能落脚, 把这个点标记为 2
        Mark(now_cur);

        //判断是否为出口
        if( isExit(now_cur, entry) )
        {
            //是出口, 直接返回
            printf("找到了一条路径\n");
            return ;
        }

        //遍历它的 上右下左 判断能否落脚
        point up = {now_cur.row-1, now_cur.col};
        assistRecursion(up, entry);

        point right = {now_cur.row, now_cur.col+1};
        assistRecursion(right, entry);

        point down = {now_cur.row+1, now_cur.col};
        assistRecursion(down, entry);

        point left = {now_cur.row, now_cur.col-1};
        assistRecursion(left, entry);
    }
}

//寻找路径
void getPath(point entry)
{
    //辅助递归函数
    assistRecursion(entry, entry);
}


int main()
{
    point entry = {0, 2};//定义入口
    MazePrint(map, "打印迷宫");
    getPath(entry);
    MazePrint(map, "迷宫路线");
    printf("\n\n\n");
    printf("\n\n\n");
    printf("\n\n\n");
    return 0;
}

2, 非递归版本


//迷宫求解
//思路: 从一个入口点开始, 依次判断它的 上,右,下,左 方位能不能走 
//      如果能就直接走, 每走一步将这个位置的坐标入栈, 并且标记为2, 代表已经走过
//      若都不能走, 说明走到死路了, 要开始回溯
//      回溯时就是把入栈的坐标出栈, 即可原路返回, 每返回一步就重新再判断它的 上右下左 能不能走
//      直到找到出口
//方法一: 利用递归的函数调用栈, 保存已经走过的点
//方法二: 利用自己定义的栈, 保存已经走过的点
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

// 9*9的迷宫
#define ROW 6
#define COL 6

//定义一个迷宫地图
int map[ROW][COL] = {
    {0, 0, 1, 0, 0, 0 },
    {0, 0, 1, 1, 1, 0 },
    {1, 1, 1, 0, 1, 1 },
    {0, 0, 1, 0, 0, 0 },
    {0, 0, 1, 1, 1, 1 },
    {0, 0, 0, 0, 0, 0 },
};

//定义一个坐标点
typedef struct point
{
    int row;
    int col;
}point;

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


//方法二: 利用自己定义的栈, 保存已经走过的点


#define MAX_SIZE 100

typedef point DataType;

typedef struct stack
{
    //栈里保存的元素类型是一个结构体
    DataType data[MAX_SIZE];
    int size;
}stack;

//定义栈 并初始化
stack mystack;
stack now_path;
stack shortest_path;

//初始化栈
void stackInit(stack* stk)
{
    stk->size = 0;
}
//入栈
void stackPush(stack* stk, DataType cur)
{
    if(stk == NULL)
      return ;
    stk->data[stk->size] = cur;
    stk->size++;
}
//出栈
void stackPop(stack* stk)
{
    if(stk == NULL)
    {
        return ;
    }
    --stk->size;
}
//取栈顶元素
int stackTop(stack* stk, DataType* cur)
{
    if(stk == NULL)
      return 0;
    if(cur == NULL)
      return 0;
    if(stk->size == 0)
      return 0;
    *cur = ( stk->data[stk->size-1] );
    return 1;
}
//打印栈
void stackPrint(stack* stk, char* msg)
{
    printf("\n========%s=======\n", msg);
    if(stk == NULL)
      return ;
    for(int i=0; i<stk->size; i++)
    {
        printf("(%d, %d)\n", stk->data[i].row, stk->data[i].col);
    }
    printf("\n");
}
//判断能否落脚的函数
int canStay(point cur)
{
    if(cur.row < 0 || cur.col < 0 || cur.row > ROW || cur.col > COL)
    {
        //点越界
        return 0;
    }

    //点的值是 0 或 2 则不能走
    if(map[cur.row][cur.col] == 0 || map[cur.row][cur.col] == 2)
    {
        return 0;
    }

    //点的值是 1 则能走
    if(map[cur.row][cur.col] == 1)
    {
        return 1;
    }
}

//遍历判断 上右下左 能否落脚的函数
//point CW_Traversal(point cur)
//{
//    //定义上右下左 4个点
//    point up = {cur.row-1, cur.col};
//    point right = {cur.row, cur.col+1};
//    point down = {cur.row+1, cur.col};
//    point left = {cur.row, cur.col-1};
//
//    if( canStay(up) )
//    {
//        return up;
//    }
//    if( canStay(right) )
//    {
//        return right;
//    }
//    if( canStay(down) )
//    {
//        return down;
//    }
//    if( canStay(left) )
//    {
//        return left;
//    }
//    //如果都能落脚, 就返回当前点
//    return cur;
//}

//标记已经走过的点
void Mark(point cur)
{
    map[cur.row][cur.col] = 2;
}

//判断当前点是不是出口
int isExit(point cur, point entry)
{
    //如果是入口, 肯定不是出口
    if(cur.row == entry.row && cur.col == entry.col)
      return 0;

    //除此之外
    //点的坐标在边界, 就说明是出口
    else if(cur.row == 0 || cur.col == 0 || cur.row == ROW-1 || cur.col == COL-1)
      return 1;

    return 0;
}

//获取路径辅助函数
void MazeSlove(point now_cur, point entrance)
{
    //判断当前点能否落脚
    if( canStay(now_cur) )
    {
        //如果能落脚, 把这个点标记为 2
        Mark(now_cur);

        //然后入栈
        stackPush(&mystack, now_cur);

        while(1)
        {
            point cur;
            //取栈顶元素
            int ret =  stackTop(&mystack, &cur);
            //打印出走过的坐标
            printf("cur : (%d, %d)\n", cur.row, cur.col);
            if(ret == 0)
            {
                //栈为空, 回溯结束了
                return ;
            }
            //判断这个栈顶的点是不是出口
            if( isExit(cur, entrance) )
            {
                //是出口,则说明找到了一条路线,直接返回
                printf("找到了一条路线\n");
                return ;
            }

            //上
            point up = {cur.row-1, cur.col};
            //先判断能否落脚
            if( canStay(up) )
            {
                //标记为2
                Mark(up);
                stackPush(&mystack, up);
                continue;
            }

            //右
            point right = {cur.row, cur.col+1};
            if( canStay(right) )
            {
                //标记为2
                Mark(right);
                stackPush(&mystack, right);
                continue;
            }

            //下
            point down = {cur.row+1, cur.col};
            if( canStay(down) )
            {
                //标记为2
                Mark(down);
                stackPush(&mystack, down);
                continue;
            }

            //左
            point left = {cur.row, cur.col-1};
            if( canStay(left) )
            {
                //标记为2
                Mark(left);
                stackPush(&mystack, left);
                continue;
            }

            //如果4个点都不能走, 就让当前点出栈, 开始回溯
            stackPop(&mystack);
        }
    }
}

//获取路径主函数
void getPath(point entry)
{
    MazeSlove(entry, entry);
}

//路径替换函数
void replacePath(stack* dst, stack* src)
{
    if(dst == NULL || src == NULL)
      return ;
    //先把 shortest_path 里的元素清空
    //memset(dst, 0, sizeof(point)*src->size + 1);
    dst->size = 0;

    for(int i=0; i<src->size; i++)
    {
        dst->data[i] = src->data[i];
        dst->size++;
        //printf("(%d, %d)\n", dst->data[i].row, dst->data[i].col);
    }
}

//求多出口迷宫的最短路径 
void getShortestPath(point now_cur, point entry, stack* now_path, stack* shortest_path)
{
    //判定当前点能否落脚
    if( canStay(now_cur) )
    {
        Mark(now_cur);
        stackPush(now_path, now_cur);
        loop: while(1)
        {
            point cur;
            int ret = stackTop(now_path, &cur);
            if(ret == 0)
            {
                stackPrint(shortest_path, "最短路径");
                //栈为空 回溯结束了
                return ;
            }

            //判断当前点是不是出口
            if( isExit(cur, entry) )
            {
                //找到一条路径以后, 拿它和 shortest_path 对比
                //如果当前路径比 shortest_path 短, 或者 shortest_path 为空
                //就用 now_path 替换 shortest_path
                if( now_path->size < shortest_path->size || shortest_path->size == 0 )
                {
                    //替换路径函数
                    replacePath(shortest_path, now_path);
                }
                //如果now_path 不比 shortest_path 更短
                //则继续找其他路径
                stackPop(now_path);
                goto loop;
            }

            //如果当前点不是出口
            //就遍历周围的 4 个点

            //上
            point up = {cur.row-1, cur.col};
            //先判断能否落脚
            if( canStay(up) )
            {
                //标记为2
                Mark(up);
                stackPush(now_path, up);
                continue;
            }

            //右
            point right = {cur.row, cur.col+1};
            if( canStay(right) )
            {
                //标记为2
                Mark(right);
                stackPush(now_path, right);
                continue;
            }

            //下
            point down = {cur.row+1, cur.col};
            if( canStay(down) )
            {
                //标记为2
                Mark(down);
                stackPush(now_path, down);
                continue;
            }

            //左
            point left = {cur.row, cur.col-1};
            if( canStay(left) )
            {
                //标记为2
                Mark(left);
                stackPush(now_path, left);
                continue;
            }

            //若4个点都不能落脚
            stackPop(now_path);
        }

    }
}

void getShortestPath_main(point entry)
{
    getShortestPath(entry, entry, &now_path, &shortest_path);
}

void TestReplace()
{
    point cur1 = {1,2};
    point cur2 = {1,3};
    point cur3 = {1,4};
    point cur4 = {1,5};

    stackPush(&now_path, cur1);
    stackPush(&now_path, cur2);
    stackPush(&now_path, cur3);
    stackPush(&now_path, cur4);
    replacePath(&shortest_path, &now_path);
    stackPrint(&now_path, "打印当前路径");
    stackPrint(&shortest_path, "打印最短路径");
}




void TestShortestPath()
{
    stackInit(&now_path);
    stackInit(&shortest_path);

    point entry = {0, 2};
    MazePrint(map, "打印迷宫");
    getShortestPath_main(entry);
    MazePrint(map, "迷宫路线");

}

void TestGetPath()
{
    stackInit(&mystack);
    point entry = {0, 2};//定义入口
    MazePrint(map, "打印迷宫");
    getPath(entry);
    MazePrint(map, "迷宫路线");
}

int main()
{
    //TestGetPath();
    //TestReplace();
    TestShortestPath();
    printf("\n\n\n");
    printf("\n\n\n");
    printf("\n\n\n");
    return 0;
}

这里写图片描述
但是如果迷宫带环, 上面的方法就会出问题了
这里写图片描述
这时, 求出了错误的结果, 所以我们需要改变一些规则
请看下面的带环求多出口迷宫的最短路径方法

求带环的多出口迷宫的最短路径

//迷宫求解
//思路: 从一个入口点开始, 依次判断它的 上,右,下,左 方位能不能走 
//      如果能就直接走, 每走一步将这个位置的坐标入栈, 并且标记为2, 代表已经走过
//      若都不能走, 说明走到死路了, 要开始回溯
//      回溯时就是把入栈的坐标出栈, 即可原路返回, 每返回一步就重新再判断它的 上右下左 能不能走
//      直到找到出口
//方法一: 利用递归的函数调用栈, 保存已经走过的点
//方法二: 利用自己定义的栈, 保存已经走过的点
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

// 9*9的迷宫
#define ROW 6
#define COL 6

//定义一个迷宫地图
int map[ROW][COL] = {
    {0, 0, 1, 0, 0, 0 },
    {0, 0, 1, 1, 1, 0 },
    {1, 1, 1, 0, 1, 1 },
    {0, 0, 1, 0, 1, 0 },
    {0, 0, 1, 1, 1, 1 },
    {0, 0, 0, 0, 0, 0 },
};

//定义一个坐标点
typedef struct point
{
    int row;
    int col;
}point;

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


//方法二: 利用自己定义的栈, 保存已经走过的点


#define MAX_SIZE 100

typedef point DataType;

typedef struct stack
{
    //栈里保存的元素类型是一个结构体
    DataType data[MAX_SIZE];
    int size;
}stack;

//定义栈 并初始化
stack mystack;
stack now_path;
stack shortest_path;

//初始化栈
void stackInit(stack* stk)
{
    stk->size = 0;
}
//入栈
void stackPush(stack* stk, DataType cur)
{
    if(stk == NULL)
      return ;
    stk->data[stk->size] = cur;
    stk->size++;
}
//出栈
void stackPop(stack* stk)
{
    if(stk == NULL)
    {
        return ;
    }
    --stk->size;
}
//取栈顶元素
int stackTop(stack* stk, DataType* cur)
{
    if(stk == NULL)
      return 0;
    if(cur == NULL)
      return 0;
    if(stk->size == 0)
      return 0;
    *cur = ( stk->data[stk->size-1] );
    return 1;
}
//打印栈
void stackPrint(stack* stk, char* msg)
{
    printf("\n========%s=======\n", msg);
    if(stk == NULL)
      return ;
    for(int i=0; i<stk->size; i++)
    {
        printf("(%d, %d)\n", stk->data[i].row, stk->data[i].col);
    }
    printf("\n");
}
//判断能否落脚的函数
int canStay(point cur)
{
    if(cur.row < 0 || cur.col < 0 || cur.row > ROW || cur.col > COL)
    {
        //点越界
        return 0;
    }

    //点的值是 0 或 2 则不能走
    if(map[cur.row][cur.col] == 0 || map[cur.row][cur.col] == 2)
    {
        return 0;
    }

    //点的值是 1 则能走
    if(map[cur.row][cur.col] == 1)
    {
        return 1;
    }
}


//标记已经走过的点
void Mark(point cur)
{
    map[cur.row][cur.col] = 2;
}

//判断当前点是不是出口
int isExit(point cur, point entry)
{
    //如果是入口, 肯定不是出口
    if(cur.row == entry.row && cur.col == entry.col)
      return 0;

    //除此之外
    //点的坐标在边界, 就说明是出口
    else if(cur.row == 0 || cur.col == 0 || cur.row == ROW-1 || cur.col == COL-1)
      return 1;

    return 0;
}


//路径替换函数
void replacePath(stack* dst, stack* src)
{
    if(dst == NULL || src == NULL)
      return ;
    //先把 shortest_path 里的元素清空
    //memset(dst, 0, sizeof(point)*src->size + 1);
    dst->size = 0;

    for(int i=0; i<src->size; i++)
    {
        dst->data[i] = src->data[i];
        dst->size++;
        //printf("(%d, %d)\n", dst->data[i].row, dst->data[i].col);
    }
}

//canStayWithCycle 带环判定落脚函数
int canStayWithCycle(point now_cur, point pre_cur)
{
    //点在地图外 不能落脚
    if(now_cur.row < 0 || now_cur.col < 0 || now_cur.row > ROW || now_cur.col > COL)
    {
        return 0;
    }
    //取now_cur的值
    int now_cur_value = map[now_cur.row][now_cur.col];
    //如果 now_cur_value 是 0, 则不能走
    if(now_cur_value == 0)
    {
        return 0;
    }
    //如果 now_cur_value 是1, 则可以直接走
    if(now_cur_value == 1)
    {
        return 1;
    }
    //now_cur的值大于pre_cur的值 + 1, 则能走
    else
    {
        int pre_cur_value = map[pre_cur.row][pre_cur.col];
        if(now_cur_value > pre_cur_value + 1)
        {
            return 1;
        }
        return 0;
    }
}

//带环标记函数
void markWithCycle(point now_cur, point pre_cur, point entry)
{
    //如果当前点是入口, 直接标记为2
    if(now_cur.row == entry.row && now_cur.col == entry.col)
    {
        map[now_cur.row][now_cur.col] = 2;
    }
    else
    {
        //当前点应该标记为它的上一个点的值 + 1
        map[now_cur.row][now_cur.col] = map[pre_cur.row][pre_cur.col] + 1;
    }
}


//求带环的多出口迷宫的最短路径
void getShortestPathWithCycle(point now_cur, point pre_cur, point entry, stack* now_path, stack* shortest_path)
{
    //判定当前点能否落脚(判定规则改变了)
    if( canStayWithCycle(now_cur, pre_cur) )
    {
        //标记当前点(规则也变了)
        markWithCycle(now_cur, pre_cur, entry);
        //当前点入栈
        stackPush(now_path, now_cur);
        loop: while(1)
        {
            point cur;
            int ret = stackTop(now_path, &cur);
            if(ret == 0)
            {
                stackPrint(shortest_path, "最短路径");
                //栈为空 回溯结束了
                return ;
            }
            pre_cur = cur;

            //判断当前点是不是出口(规则没变)
            if( isExit(cur, entry) )
            {
                //找到一条路径以后, 拿它和 shortest_path 对比
                //如果当前路径比 shortest_path 短, 或者 shortest_path 为空
                //就用 now_path 替换 shortest_path
                if( now_path->size < shortest_path->size || shortest_path->size == 0 )
                {
                    //替换路径函数
                    replacePath(shortest_path, now_path);
                }

                //则继续找其他路径
                stackPop(now_path);
                goto loop;
            }

            //如果当前点不是出口
            //就遍历周围的 4 个点

            //上
            point up = {cur.row-1, cur.col};
            //先判断能否落脚
            if( canStayWithCycle(up, pre_cur) )
            {
                //标记
                markWithCycle(up, pre_cur, entry);
                stackPush(now_path, up);
                continue;
            }

            //右
            point right = {cur.row, cur.col+1};
            if( canStayWithCycle(right, pre_cur) )
            {
                //标记
                markWithCycle(right, pre_cur, entry);
                stackPush(now_path, right);
                continue;
            }

            //下
            point down = {cur.row+1, cur.col};
            if( canStayWithCycle(down, pre_cur) )
            {
                //标记
                markWithCycle(down, pre_cur, entry);
                stackPush(now_path, down);
                continue;
            }

            //左
            point left = {cur.row, cur.col-1};
            if( canStayWithCycle(left, pre_cur) )
            {
                //标记
                markWithCycle(left, pre_cur, entry);
                stackPush(now_path, left);
                continue;
            }

            //若4个点都不能落脚
            stackPop(now_path);
        }
    }
}

void getShortestPath_main(point entry)
{
    point pre_cur = {-1, -1};
    getShortestPathWithCycle(entry, pre_cur, entry, &now_path, &shortest_path);
}




void TestShortestPath()
{
    stackInit(&now_path);
    stackInit(&shortest_path);
    point entry = {0, 2};
    MazePrint(map, "打印迷宫");
    getShortestPath_main(entry);
    MazePrint(map, "迷宫路线");
}


int main()
{
    TestShortestPath();
    printf("\n\n\n");
    printf("\n\n\n");
    printf("\n\n\n");
    return 0;
}

这里写图片描述
此时每个出口的值 - 1, 便是从入口到出口走的步数
可以看出, 最短的走了 5 步, 打印出的路线也正确

阅读更多
想对作者说点什么?

博主推荐

换一批

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