迷宫求解_数据结构_C/C++

该文详细介绍了如何使用C语言实现基于栈的迷宫求解算法,包括栈的定义、表示和实现,以及迷宫数据结构的定义。通过初始化栈、判断当前位置是否可以通过、留下路径标记等步骤,寻找从起点到终点的路径。如果找到路径,则打印路径,否则表示无通路。
摘要由CSDN通过智能技术生成

思路:

1.    问题求解需要用到数据结构——栈,所以需要栈的定义、表示和实现
        1)    栈的定义
        栈所需要的数据元素的定义
        2)    栈的表示
        栈的存储结构使用什么?顺序or链式?在此采用链式
        栈的存储结构的描述,用类型定义来描述。
        3)    栈的实现
        栈的基本操作的函数,需要自己写。
        4)    但这些实现之前还需要一些常量、类型的定义。
2.    问题的求解,即算法,算法思路详见代码。

  • 如果当前位置“可通”,则纳入“当前路径”,并继续朝下一个位置探索,即切换下一个位置为当前位置
  • 如果当前位置不可通,则应沿“来向”退回到前一通道块,然后朝其它方向继续探索
  • 如果该通道块的四周方块均不可通过,则应从当前路径上删除该通道块
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//#include <malloc.h>			//动态存储分配函数头文件,用于栈的存储空间的分配
#include <sys/malloc.h>         //mac需要这个头文件

//数据结构——栈需要使用的常量及类型
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10

typedef int Status;
#define OK 1
#define ERROR 0
// #define OVERFLOW -2

//算法——迷宫求解需要使用的常量和类型
#define RANGE 100               //迷宫的最大长度和宽度
#define ROW 10                  //迷宫的行数
#define COL 10                  //迷宫的列数
typedef int DirectiveType;      //下一个通道的方向
typedef struct{
    int row;
    int col;
}PosType;                       //坐标(row,col)类型
typedef struct{
    int m, n;                   //迷宫的行数和列数
    int arr[RANGE][RANGE];      //迷宫的数组(只是有这么大,但是并没有都使用)
}MazeType;                      //迷宫的类型,为什么需要迷宫的类型,因为我们在解决问题之前,需要向算法的函数实现传入迷宫,而迷宫就要在电脑中有它的表示,也就是迷宫的数据结构。

// 栈的定义、表示和实现
// 栈的定义,即数据元素的类型(SElemType,在程序中需自己定义),存储在栈中的应该是【从入口到当前位置前一块】的【已经走过的路径中的每一块】(为什么是当前位置的前一块,因为当前位置正在进行判定,还没有加入到这个路径中)
typedef struct{
    int step;                   //这是第几步,即该位置在路径中的序号
    PosType seat;               //该位置的坐标
    DirectiveType di;           //从该位置往下一个要探寻的位置的方向
}SElemType;
//栈的表示,即栈的存储结构的描述,用类型定义(数据类型)来描述,这里使用链栈,所以是链栈的描述
typedef struct{
    SElemType *base;    //栈底
    SElemType *top;     //栈顶
    int stacksize;      //栈分配的存储空间的大小
}SqStack;             //顺序栈类型,定义顺序栈(虽然用了指针,但仍然是顺序栈,即数组也可以用指针!)
//栈的实现,即栈的基本操作
//初始化栈,即让栈的各成分变量均有值,即为栈分配存储空间并初始化各成分变量。
Status InitStack(SqStack &S){
    S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));  //是顺序栈,因为是用数组来描述的,并且采用的是动态数据,即动态分配存储空间。
    if(!S.base){
        exit(OVERFLOW);
    }
    S.top = S.base;
    S.stacksize = STACK_INIT_SIZE;
    return OK;
}
//当栈非空时,取栈顶元素
Status GetTop(SqStack S, SElemType &e){
    if(S.top == S.base){    //判断栈是否为空
        return ERROR;
    }
    e = *(S.top - 1);
    return OK;
}
//入栈
Status PushStack(SqStack &S, SElemType e){
    //只要是要进行插入操作,都需要:1.检查参数2.检查空间3.插入元素4.修改长度等
    //检查空间
    if(S.top - S.base >= S.stacksize){
        S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
        if(!S.base){
            exit(OVERFLOW);
        }
        S.top = S.base + S.stacksize;
        S.stacksize += STACKINCREMENT;
    }
    *(S.top++) = e;
    return OK;
}
//出栈
Status PopStack(SqStack &S, SElemType &e){
    //删除元素之前都需要判断是否为空
    if(S.top == S.base){
        return ERROR;
    }
    e = *(--S.top);
    return OK;
}
//判断栈是否为空
Status StackEmpty(SqStack S){
    if(S.top == S.base){
        return OK;  //1
    }
    return ERROR;   //0
}
//销毁栈,就是要释放顺序栈的数组所占用的空间,也就是free()释放动态分配的存储空间【free只能释放动态分配malloc、calloc、realloc的空间,不能释放非动态分配的空间】,并且让栈的top、base为NULL、stacksize为0。
Status DestroyStack(SqStack &S){
    free(S.base);   //释放S.base所指向的存储空间,还给系统。S.base所指向的空间是malloc或者realloc动态分配的,所以整个数组都会被释放。
    S.top = S.base = NULL;
    S.stacksize = 0;
    return OK;
}

//算法的实现
//初始化迷宫
Status InitMaze(MazeType &maze, int a[ROW][COL], int row, int col){ //row和col是迷宫的真实的行数和列数,应该是10
	int i, j;
	//设置迷宫maze的初值,包括加上边缘一圈的值
	for(i = 1; i < row; i++){
		for(j = 1; j < col; j++){
			maze.arr[i][j] = a[i][j];
		}
	}
	//加上围墙
	for(i = 0; i < row; i++){
		maze.arr[0][i] = maze.arr[row - 1][i] = 1;		//第0行和第9行为1
	}
	for(j = 0; j < col; j++){
		maze.arr[j][0] = maze.arr[j][col - 1] = 1;		//第0列和第9列为1
	}
	return OK;
}

//判断当前位置是否可以通过,传入参数是迷宫类型的变量,以及当前位置的坐标。
Status Pass(MazeType maze,PosType curpos){
	//当节点为0时,表示有路
	if(maze.arr[curpos.row][curpos .col] == 0){ //只有等于0才表示可通,为1表示障碍物
		return OK;
	}else {
		return ERROR;
	}
}

//留下标记,表示这个位置已经探索过,已经走过,不管这个位置之后还在不在路径中(栈中),都不要再走这一块了。
//留下标记,也就是把maze类型变量的成员的数组中这个位置设成2,表示已经探索过,和Pass函数一样
//注意这里的2,只有在栈中的元素才有资格被标记成2,之后在打印迷宫的时候,2就是要打印的通路。因为他现在入栈,被打成2,若他之后出栈了,就会被打成其他的,若他不出栈,就不会变,所以2就是路径中的块的标志。
Status FootPrint(MazeType &maze,PosType curpos){
	maze.arr[curpos.row][curpos .col] = 2;			//走过且走得通
	return OK;
}

//创建元素e
SElemType CreateSElem(int step, PosType pos, int di){
	SElemType e;
	e.step = step;
	e.seat = pos;
	e.di = di;
	return e;
}

//判断是不是出口
Status PosEqual(PosType pos1, PosType pos2){
	
	if(pos1.row == pos2.row && pos1.col == pos2.col){
		return OK;
	}else
		return ERROR;
}

//获取下一个当前位置
PosType NextPos(PosType curpos ,DirectiveType di){
	PosType pos = curpos ;
	switch(di){
		case 1:pos.col++;break;			//右
		case 2:pos.row++;break;			//下
		case 3:pos.col--;break;			//左
		case 4:pos.row--;break;			//上
	}
	return pos;
}

//标记走不通,迷宫数组中,3表示走不通
//留下不能通过的标记
Status MarkPrint(MazeType &maze,PosType curpos){
	maze.arr[curpos.row][curpos.col] = 3;
	return OK;
}

//打印迷宫
//打印路径
void PrintMaze(MazeType maze,int row,int col){
	int i, j;
	printf(" ");
    for (i = 0; i < col; i++)                    //打印列数名
        printf("%d ", i);
    printf("\n");
	for (i = 0; i < row; i++){
		printf("%d", i);                      //打印行数名
        for (j = 0; j < col; j++){
		    //printf("%d ",maze.arr[i][j]);
            switch (maze.arr[i][j]){
				case 0:printf("  ");break;              //没走过,但是通路            
				case 1:printf("■ ");break;              //墙,障碍物
				case 2:printf("# ");break;              //走过且走得通             
				case 3:printf("* ");break;              //走过但走不通,死胡同               
				default:break;
           }   
       }
       printf("\n");
    }
}

//求解迷宫maze中,从入口start到出口end的一条路径的函数,这是本题算法最关键的部分
Status MazePath(MazeType &maze, PosType start, PosType end){
    //在该算法中,因为要用到栈,所以先定义一个栈并初始化。
    SqStack S;
    InitStack(S);

    SElemType e;

    //1.先将起点设置为当前位置
    PosType curpos = start;
    //2.当前步数设置为1,即搜索第一步
    int curstep = 1;

    //开始循环搜索每一步,直到栈为空,即没有找到一条路径,否则都要继续循环找路径。只要没有路径,就找不到路径,就一定会返回到起点start,并且起点start也会出栈,使栈为空,所以栈为空可以作为找不到路径的标志。只要栈不为空,就说明还有可能找得到路径,就要继续循环找。
    do{
        //1.如果当前位置可以通过,即是通道块(不是障碍物),并且是未曾走过的路径,那么当前位置入栈,成为路径中的一步。
        if(Pass(maze, curpos)){
            FootPrint(maze, curpos);    //留下标记,表示这一步已经到达过,之后要探索路径就不要探索这一块了,因为如果第一次到达这一块都没有走通,那之后到达这一块也不能走通。【这里需要理解一下!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!】
            //当前位置入栈,但要入栈,要先把当前位置变成一个数据元素的结构才可以
            e = CreateSElem(curstep, curpos, 1);    //创建元素
            PushStack(S, e);
            //判断是否到达终点了,如果到达了,说明找到了路径,直接返回
            if(PosEqual(curpos, end)){
                return OK;
            }
            curpos = NextPos(curpos, 1);    //获取下一个当前位置,下一个当前位置就是当前栈顶元素(即路径的最后一个通道块)的di方向的位置。但是因为这里正好在进栈之后,所以di方向一定就是右边,所以直接传入参数1
            curstep++;  //因为一个已经进栈,所以步数+1
        }
        else{   //当前位置不可通
            //判断栈是否为空,为空则表示找不到路径(在此为空没有操作,因为到最后do循环退出后,就是统一的栈为空找不到路径的操作),不为空才有操作
            if(!StackEmpty(S)){
                PopStack(S, e);
                //判断出栈的这个元素有无其他可以探索的方向
                while(e.di == 4 && !StackEmpty(S)){ //四个方向都已探寻过,且仍然有路径可以走(栈非空)
                    MarkPrint(maze, e.seat);    //标记这个方向走不通
                    PopStack(S, e);     //继续出栈
                }
                //出栈直到有未探索的方向,或者栈为空
                //在此栈为空没有操作,因为到最后do循环退出后,就是统一的栈为空找不到路径的操作
                if(e.di < 4){
                    e.di++;     //因为在一个e入栈之前,di都是被初始化为1,之后就将curpos设置为e的右边的那一个位置,这时候di仍为1。所以在这里,之后每次要改变探索方向时,都要先+1,再探索。判断的时候,1,表示刚探索过右;2,表示刚探索过下;3,表示刚探索过左;4,表示刚探索过上。所以di为4就表示都探索过了,并且要探索下一个时,要先将di+1。
                    PushStack(S, e);    //因为有可以探索的方向,所以出栈的又重新入栈
                    curpos = NextPos(e.seat, e.di); //注意这里是e.seat,而不是curpos
                }
            }
        }
    }while(!StackEmpty(S));

    return ERROR;   //栈为空,才会走到这,否则在do循环中就会返回。也就是没找到路径,才会走到这一步,故返回ERROR,0,表示没找到。
}

Status main(){
	int i, j;
	PosType start ,end;			                        //开始,终点坐标
	MazeType maze;
	int a[ROW][COL] = {
		{1,1,1,1,1,1,1,1,1,1},
		{1,0,0,0,1,0,0,1,0,1},
		{1,0,0,1,0,1,0,1,1,1},
		{1,1,0,0,0,1,0,0,0,1},
		{1,0,1,1,0,0,1,1,0,1},
		{1,1,0,0,1,0,0,0,1,1},
		{1,0,1,0,0,0,1,1,0,1},
		{1,0,1,0,1,1,1,1,0,1},
		{1,1,0,0,0,0,0,0,0,1},
		{1,1,1,1,1,1,1,1,1,1}
	};                                                  //迷宫数组,是一个二维数组,1表示障碍物,0表示非障碍物,10*10
	printf("\n原始迷宫如下:\n");
	printf("(其中‘1’表示墙,‘0’表示通道)\n");
    for(i = 0; i < 10; i++){
		for(j = 0; j < 10; j++){
			printf("%d ",a[i][j]);
		}
		printf("\n");
	}                                                   //打印迷宫数组

	InitMaze(maze,a,ROW,COL);			                //初始化迷宫,传入参数为迷宫类型的变量、迷宫数组、行数、列数
	start.row = 1;                      				//给定迷宫起点坐标(1,1)
	start.col = 1;
	end.row = 8;
	end.col = 8;						                //给定迷宫终点坐标(8,8),因为第0、9行/列都是围墙
    
    //迷宫和起点终点已经给出了,可以开始寻找路径了

	if (MazePath(maze, start, end)){                     //如果找到一条路径
       printf("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
       printf("\n求解迷宫路径如下:\n");
	   printf("(其中'#'表示求解路径,'*'表示死胡同)\n");

       PrintMaze(maze, ROW,COL);                        //打印迷宫路径

    }else
		printf("\n从入口(1,1)到出口(8,8)没有通路!\n");

	return OK;
}

参考:https://www.cnblogs.com/a1982467767/p/8889583.html 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值