迷宫求解(C语言版)

问题描述

迷宫问题是取自心理学的一个古典实验。在该实验中,把一只老鼠从一个无顶大盒子的门放入,在盒子中设置了许多墙,对行进方向形成了多处阻挡。盒子仅有一个出口,在出口处放置一块奶酪,吸引老鼠在迷宫中寻找道路以到达出口。对同一只老鼠重复进行上述实验,一直到老鼠从入口走到出口,而不走错一步。老鼠经过多次试验最终学会走通迷宫的路线。设计一个计算机程序对任意设定的矩形迷宫如下图所示,求出一条从入口到出口的通路,或得出没有通路的结论。

这里写图片描述

思路分析

要解决迷宫问题,首先第一步就是要利用一种方式把迷宫给存储起来。在这里采用二维数组。由于计算机解迷宫时,常用的是“穷举求解”的办法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路返回,换一个方向继续探索,直至所有可能的通路都探索为止。为了保证在任何位置上都能按原路返回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。
因此,在迷宫通路的算法中要运用栈。所以,在实际中我们用方块图表示迷宫,’#’代表墙,’0’代表路,‘$’可表示通路,‘@’已探索过的位置。
假设“当前位置”指的是“在搜索过程中某一时刻所在图中某个方块位置”,则求迷宫中一条路径的算法基本思想是:若当前位置“可通”,则纳入“当前路径”,并继续朝“下一位置”探索,即切换“下一位置”为“当前位置”,如此重复直至到达出口;若当前位置“不可通”则应顺着“来向”退回到“前一通道块”,然后朝着除“来向”之外的其他地方继续探索;若该通道块的四周4个方向(东、南、西、北)上相邻的方块。假设以栈S记录“当前路径”,则栈顶中存放的是“当前路径上最后一个通道块”。由此,“纳入路径”的操作即为“当前位置入栈”,“从当前路径上删除前一通道块”的操作即为“出栈”。

求解迷宫问题的算法可以简单描述如下:

设定当前位置的初值为入口位置:
do{
若当前位置可通,
则{ 将当前位置插入栈顶;             //纳入路径
若该位置是出口位置,则结束;      //求得路径存放在栈中
否则切换当前位置的东邻方块为新的当前位置;
}
否则,
    若栈不空且栈顶位置尚有其他方向未经探索,
      则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
    若栈不空但栈顶位置四周均不可通,
      则{ 删去栈顶位置;              //从路径中删去该通道块
           若栈不空,则重新测试新的栈顶位置,
            直至找到一个可通的相邻块或出栈至栈空;
}
}while(栈不空);

程序流程图

这里写图片描述

代码实现

Maze.h

#ifndef __MAZE_H__
#define __MAZE_H__//防止头文件被多次引入

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h>
#pragma warning(disable:4996)//消除警告

#define MAZEROW 10//迷宫行数
#define MAZECOL 10//迷宫列数
#define MAXSIZE 100//最大元素数

//迷宫中的坐标位置
typedef struct Position
{
    int x;//行号
    int y;//列号
}PosType, *PPosType;
//栈元素类型
typedef struct
{
    PosType seat;//坐标位置
    int dir;//dir = 1,2,3,4分别表示东、南、西、北四个方向
}SElemType;
//顺序栈类型
typedef struct Stack
{
    SElemType data[MAXSIZE];
    int top;//栈顶
}MazeStack, *pMazeStack;

//初始化栈
void InitMazeStack(pMazeStack sm);
//入栈
void PushStack(pMazeStack sm, SElemType data);
//出栈,并将出栈元素保存
void PopStack(pMazeStack sm, SElemType *tmp);
//判断栈是否为空,为空返回1,非空返回0
int EmptyStack(pMazeStack sm);
//走过的地方做标记
void FootPrint(PosType curpos, char maze[MAZEROW][MAZECOL]);
//走不通的地方做标记
void MarkPrint(PosType curpos, char maze[MAZEROW][MAZECOL]);
//找下一个位置作为当前位置,顺时针开始
PosType NextPos(PosType curpos, int dir);
//判断当前位置是否可以通过(没墙)
int IsPass(PosType curpos,char maze[MAZEROW][MAZECOL]);
//求一条从入口到出口的位置
int MazePath(char maze[MAZEROW][MAZECOL], PosType start, PosType end);
//打印迷宫当前的状态
void PrintMaze(char maze[MAZEROW][MAZECOL]);
//打印路径
void PrintPath(char maze[MAZEROW][MAZECOL]);
//打印坐标
void PrintSeat(char maze[MAZEROW][MAZECOL]);
//探索路径
void Exploration(char maze[MAZEROW][MAZECOL], PosType start, PosType end);

#endif //__MAZE_H__

Maze.c

#include "Maze.h"
//初始化栈
void InitMazeStack(pMazeStack sm)
{
    sm->top = -1;
}
//入栈
void  PushStack(pMazeStack sm, SElemType data)
{
    if (sm->top == MAXSIZE - 1)
    {
        printf("此栈已满,不能入栈\n");
    }
    else
    {
        sm->top++;
        sm->data[sm->top] = data;
    }
}
//出栈,并将出栈元素保存
void PopStack(pMazeStack sm, SElemType *tmp)
{
    if (sm->top == -1)
    {
        printf("栈空,不能出栈\n");
    }
    else
    {
        *tmp = sm->data[sm->top];
        sm->top--;
    }
}
//判断栈是否为空,为空返回1,非空返回0
int EmptyStack(pMazeStack sm)
{
    return sm->top == -1;
}
//走过的地方做标记‘$’
void FootPrint(PosType curpos, char maze[MAZEROW][MAZECOL])
{
    maze[curpos.x][curpos.y] = '$';
}
//走不通的地方做标记‘@’
void MarkPrint(PosType curpos, char maze[MAZEROW][MAZECOL])
{
    maze[curpos.x][curpos.y] = '@';
}
//找下一个位置作为当前位置,顺时针开始
PosType NextPos(PosType curpos, int dir)
{
    switch (dir)
    {
    case 1:
        //‘1’代表东,行号不变,列号加1
        curpos.y++;
        break;
    case 2:
        //‘2’代表南,行号加1,列号不变
        curpos.x++;
        break;
    case 3:
        //‘3’代表西,行号不变,列号减1
        curpos.y--;
        break;
    case 4:
        //‘4’代表北,行号减1,列号不变
        curpos.x--;
        break;
    default:
        printf("参数有误!\n");
        break;
    }
    return curpos;
}
//判断当前位置是否可以通过,既为‘0’可以通过。('$'代表已经走过、‘@’代表死胡同)
//返回1可以通过,返回0不能通过
int IsPass(PosType curpos, char maze[MAZEROW][MAZECOL])
{
    if (maze[curpos.x][curpos.y] == '0')
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
//求一条从入口到出口的路径
int MazePath(char maze[MAZEROW][MAZECOL], PosType start, PosType end)
{
    MazeStack sm;
    InitMazeStack(&sm);
    //设置入口坐标为当前位置
    PosType curpos = start;
    do
    {
        //如果当前位置可走
        if (IsPass(curpos, maze))
        {
            FootPrint(curpos, maze);//做一个已经走过的标记
            SElemType data;
            data.seat = curpos;
            data.dir = 1;
            PushStack(&sm, data);//将当前位置加入路径,入栈
            //如果当前位置是出口,则结束
            if (curpos.x == end.x&&curpos.y == end.y)
            {
                return 1;
            }
            //否则切换当前位置的东边为下一个位置
            curpos = NextPos(curpos, 1);
        }
        else//当前位置不能通过
        {
            //栈不为空,还有路径可查
            if (!EmptyStack(&sm)) 
            {
                SElemType data;
                PopStack(&sm, &data);//出栈一次,保留数据
                //判断出栈位置的四面是否全被探索,是则标记为死胡同在出栈一次
                while (data.dir == 4 && !EmptyStack(&sm))
                {
                    MarkPrint(data.seat, maze);
                    PopStack(&sm, &data);
                }
                //上一次出栈的位置必然是其他方向没有探索
                //换下一方向探索
                if (data.dir < 4)
                {
                    data.dir++;
                    PushStack(&sm, data);//将当前位置入栈纳为路径
                    curpos = NextPos(data.seat, data.dir);//切换当前位置为该方向上的东邻位置
                }
            }
        }
        printf("<--------------------------------------------------------->\n");
        printf("开始探索迷宫:('$'代表路径,'@'代表探索过但是不通的地方)\n");
        printf("<--------------------------------------------------------->\n");
        PrintMaze(maze);
        Sleep(1000);//睡眠1000毫秒
        system("cls");//清屏
    } while (!EmptyStack(&sm));//栈不为空,一直存在位置探索
    return 0;
}
//遍历二维数组,打印迷宫当前的状态
void PrintMaze(char maze[MAZEROW][MAZECOL])
{
    int i = 0;
    for (i = 0; i < MAZEROW; i++)
    {
        int j = 0;
        for (j = 0; j < MAZECOL; j++)
        {
            printf("%c ", maze[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}
//打印路径
void PrintPath(char maze[MAZEROW][MAZECOL])
{
    int i = 0;
    for (i = 0; i < MAZEROW; i++)
    {
        int j = 0;
        for (j = 0; j < MAZECOL; j++)
        {
            if (i == 0 || j == 0 || i == MAZEROW - 1 || j == MAZECOL - 1 || maze[i][j] == '$')
            {
                printf("%c ", maze[i][j]);
            }
            else
            {
                printf("  ");
            }
        }
        printf("\n");
    }
    printf("\n");
}
//打印通路坐标
void PrintSeat(char maze[MAZEROW][MAZECOL])
{
    int i = 0;
    for (i = 0; i < MAZEROW; i++)
    {
        int j = 0;
        for (j = 0; j < MAZECOL; j++)
        {
            if (maze[i][j] == '$')
            {
                printf("(%d,%d)-->", i, j);
            }
        }
    }
    printf("over\n");
}

test.c

#include "Maze.h"
//菜单
void menu()
{
    printf("****************************************************\n");
    printf("**************★欢迎来到迷宫探索★******************\n");
    printf("****************  ●0.退出探索  ********************\n");
    printf("****************  ●1.迷宫地图  ********************\n");
    printf("****************  ●2.迷宫探索  ********************\n");
    printf("****************************************************\n");
}
//探索路径
void Exploration(char maze[MAZEROW][MAZECOL], PosType start, PosType end)
{
    printf("迷宫地图:\n");
    PrintMaze(maze);//打印迷宫初态
    Sleep(2000);
    system("cls");
    if (MazePath(maze,start, end))
    {
        printf("<--------------------------------------------------------->\n");
        printf("探索成功!\n");
        printf("<--------------------------------------------------------->\n");
        printf("迷宫的现态(包括通路‘$’和已经探索过的位置'@'):\n");
        PrintMaze(maze);//打印迷宫当前状态
        printf("探索到迷宫的一条通路:\n");
        PrintPath(maze);//打印迷宫的一条通路
        printf("迷宫通路坐标:\n");
        PrintSeat(maze);//打印通路坐标
    }
    else
    {
        printf("此迷宫不存在通路!\n");
        PrintMaze(maze);
    }
}
int main()
{
    int input = 0;
    PosType start = { 0, 0 };//入口坐标
    PosType end = { 0, 0 };//出口坐标
    do
    {
        //二维数组设置迷宫地图('#'代表墙,'0'代表路),最外边的一层强是为了防止越界访问
        char maze[MAZEROW][MAZECOL] = {
            { '#', '#', '#', '#', '#', '#', '#', '#', '#', '#' },
            { '#', '0', '0', '#', '0', '0', '0', '#', '0', '#' },
            { '#', '0', '0', '#', '0', '0', '0', '#', '0', '#' },
            { '#', '0', '0', '0', '0', '#', '#', '0', '0', '#' },
            { '#', '0', '#', '#', '#', '0', '0', '0', '0', '#' },
            { '#', '0', '0', '0', '#', '0', '0', '0', '0', '#' },
            { '#', '0', '#', '0', '0', '0', '#', '0', '0', '#' },
            { '#', '0', '#', '#', '#', '0', '#', '#', '0', '#' },
            { '#', '#', '0', '0', '0', '0', '0', '0', '0', '#' },
            { '#', '#', '#', '#', '#', '#', '#', '#', '#', '#' } };
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
        {
                  int count = 3;
                  fflush(stdin);//刷新缓冲区
                  printf("地图读取中");
                  while (count--)
                  {
                      Sleep(500);
                      printf(".");
                  }
                  printf("\n");
                  printf("迷宫地图:\n");
                  PrintMaze(maze);
        }
            break;
        case 2:
            fflush(stdin);//刷新缓冲区
            printf("请输入入口坐标((1,1)-(8,8)):");
            scanf("%d,%d", &start.x, &start.y);
            printf("请输入出口坐标((1,1)-(8,8)):");
            scanf("%d,%d", &end.x, &end.y);
            Exploration(maze, start, end);
            break;
        case 0:
            exit(0);
            break;
        default:
            printf("输入有误,请重新输入:\n");
            break;
        }
    } while (input);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/hansionz/article/details/80901089