迷宫问题(C语言)

一、栈

栈又是线性表,但是它是一个受限制的线性表。线性表可以自由得删除或插入节点,但栈只能在栈顶删除(出栈)或插入(入栈)。(见下图)

顺序表的插入与删除:
顺序表的插入与删除
栈的入栈与出栈:
入栈和出栈

1.名词

栈就像是一个堆放积木的盒子,与盒子等大的积木就像是栈元素,先放进去的积木总是最后才能被取出来,它只能依次从盒子顶端开始取。
栈顶
最后入栈的 下一个元素,是栈的最上面的元素
栈底
最开始(第一个)入栈的元素,是栈的最下面的元素
栈顶指针
指向栈顶元素
栈底指针
指向栈底元素
空栈和初始栈
栈顶指针指向栈顶指针,表示该栈可能是空栈(初始化的栈是空栈)

2.栈的实现

栈元素的结构:

/* 顺序栈元素类型定义 */
typedef int SElemType;

// 顺序栈元素结构
typedef struct {
    SElemType* base;               // 栈底指针
    SElemType* top;                // 栈顶指针
    int stacksize;                 // 当前已分配的存储空间,以元素为单位
} SqStack;

其中

SElemType* base;	// 栈底指针
//栈底指针,主要用于接收动态分配空间的首地址。

SElemType* top;  	// 栈顶指针
//栈顶指针,主要用于入栈和出栈。

初始化栈

"->"是指针式访问结构体成员

typedef int Status;

/*
*初始化栈(空栈)
*初始化成功返回OK,否则返回ERROR
*/
Status InitStack(SqStack* S) {
    if(S == NULL) {
        return ERROR;
    }
    //分配栈空间
    S->base = (SElemType*) malloc(STACK_INIT_SIZE * sizeof(SElemType));
    if(S->base == NULL) {
        exit(1);//非正常结束当前进程
    }
    
    S->top = S->base;
    S->stacksize = STACK_INIT_SIZE;
    
    return OK;
}

入栈

/*
*入栈(将e压入栈)
*入栈成功返回OK,否则返回ERROR
*/
Status Push(SqStack* S, SElemType e)
{
	if (S->base == NULL || S == NULL)
		return ERROR;

	//若栈满了,则增加空间
	if (S->top - S->base >= S->stacksiaze)
	{
		S->base = (SElemType*)realloc(S->base,(S->stacksiaze + STACKINCREMENT) * sizeof(SElemType));
		if (S->base == NULL)
			exit(1);//分配失败

		S->top = S->base + S->stacksiaze;
		S->stacksiaze += STACKINCREMENT;
	}

	//将e压入栈(先入栈,栈顶指针再自增)
	*(S->top++) = e;
	return OK;
}

出栈

/*
*出栈(e存入的是出栈的内容)
*出栈成功返回OK,否则返回ERROR
*/
Status Pop(SqStack* S, SElemType* e)
{
	if (S->base == NULL || S == NULL || S->base == S->top)
		return ERROR;

	//栈顶指针先递减
	*e = *(--(S->top));//当前栈顶指针所指的地址都默认是不用地址,栈顶的前一个地址(--(S->top))才是有用地址

	return OK;
}

二、迷宫问题

迷宫一般来讲都是有外墙、迷宫内墙、通路、死胡同、方向(东南西北)、迷宫的方位(x、y坐标)、迷宫大小等等。

1.定义迷宫类型

typedef int MazeType[M][N];      // 迷宫类型
//迷宫类型
typedef enum {
    Wall,                       // 外墙
    Obstacle,                   // 迷宫内部的障碍
    Way,                        // 通路
    Impasse,                    // “死胡同”
    East, South, West, North    // 当前探索方向:东南西北
} MazeNode;

//通道信息
typedef struct {
	int x;
	int y;
	int di;//下一个访问的位置
}SElemType;

//迷宫栈
typedef struct {
    SElemType* base;               // 栈底指针
    SElemType* top;                // 栈顶指针
    int stacksize;                 // 当前已分配的存储空间,以元素为单位
} SqStack;
typedef int MazeType[M][N];      // 迷宫类型

常见的类型定义是:

typedef int** MazeType;      // 迷宫类型

二者类似但第一种是为二位数组分配了大小的二维数组,第二个是暂未分配大小的二位数组大小。(第一个已经为二位数组初始化元素为0,第二种是未初始化的二维数组)

2.迷宫分析

1.迷宫的初始化

  • 在初始化阶段将迷宫的外形确定下来,包括迷宫的外轮廓(外墙)、迷宫内的障碍(内墙)确定并打印在终端上,为了保证迷宫的不确定性和迷宫寻路的自主性,迷宫的内墙由系统的随机数发生器随机的生成内墙的位置。
  • 迷宫的墙壁都是由枚举类型来给定的,这样做的好处是代码阅读方便容易理解。
  • 迷宫的坐标是由二维数组的下标表示。

如下所示是一个4*4大小的迷宫二维数组,数组中存储的就是迷宫的类型(外墙、内墙、通路、死胡同,方向),假设1是墙壁,0是通路:

0111
1011
1000
1111

代码如下:

void InitMaze(MazeType maze, PosType* start, PosType* end)
{
	int tmp;
	srand((unsigned)time(NULL));       //初始化随机数发生器
	for (int i=0;i < M;i++)
	{
		for (int j = 0;j < N;j++)
		{	
			//墙壁生成
			if (i == 0 || j == 0 || i == M - 1 || j == N - 1)
				maze[i][j] = Wall;//外墙
			else
			{
				tmp = rand() % X;
				if (tmp == 0)
					maze[i][j] = Obstacle;//内墙
				else
					maze[i][j] = Way;//通路
			}
		}
	}
	//入口
	start->x = 1;
	start->y = 0;
	//出口
	end->x = M - 2;
	end->y = N - 1;
	//开放入口和出口
	maze[1][0] = maze[M - 2][N - 1] = Way;
	//提高寻路成功率
	maze[1][1] = maze[M - 2][N - 2] = Way;

	// 显示迷宫的初始状态
	PrintMaze(maze);
}

2.迷宫显示

利用switch函数判断迷宫类型二位数组maze的类型,根据其类型打印迷宫。
其关键点:

  • 在每一行的结束位置输出回车符。
  • 每次进入打印函数,先清屏再打印。

3.迷宫寻路

  1. 首先判断当前位置是否可以通过,判断依据是当前坐标位置是不是通路;
    1. 若当前位置可以通过,则默认先向东访问并在该坐标下留在访问的方向标记(东南西北和死胡同);
      1. 打印当前坐标的迷宫类型到终端,并且将当前位置压入栈中;
    2. 若当前位置不能通过,若栈不为空,则弹栈,回到上一个位置,上一个位置可能是寻路到了最后一个方向那么就再弹栈,再向上一个位置寻路,如果还没有寻路到最后一个方向,那么就会在当前位置寻找其他方向的路,直到寻路失败或寻路成功;
    3. 若栈为空,结束迷宫寻路,标准着寻路失败。

代码如下:

Status MazePath(MazeType maze, PosType start, PosType end)
{
	SqStack S;
	PosType curPos;
	SElemType e;

	InitStack(&S);
	
	curPos = start;//设置当前位置是入口位置

	do {
		//如果当前位置是可以通过的(该位置是从未曾探索过的通道块)
		if (Pass(maze, curPos))
		{
			//留下足迹(留下向东访问的标记)
			FootPrint(maze, curPos);

			e = Construct(curPos,East);//构造一个通道块信息并返回

			Push(&S, e);//将通道块压入栈
			if (Equals(curPos, end))
			{
				printf("\n寻路成功!!\n\n");
				return TRUE;
			}

			curPos = NextPos(curPos, East);//获取下一个要探索的位置

		}
		else
		{
			if (!StackEmpty(S))
			{
				Pop(&S, &e);//退回上一个位置

				while (e.di == North && !StackEmpty(S))
				{
					MarkPrint(maze, e.seat, Impasse);//留下死胡同印记

					Pop(&S, &e);//继续退回
				}
				if (e.di < North)
				{
					++e.di;//改变探索方向,按东南西北方向轮询

					MarkPrint(maze, e.seat, e.di);//在迷宫下留下访问标记,用来观察迷宫的状态
					Push(&S, e);//从新将该位置加入到路径只能
					curPos = NextPos(e.seat, e.di);
				}
			}
		}
	} while (!StackEmpty(S));

	printf("\n寻路失败\n\n");

	return FALSE;
}

3.运行结果

8:向北方向
4:向西方向
6:向东方向
2:向南方向
0:死胡同
*:墙壁

迷宫问题C语言_运行结果

三.全部程序和参考书籍

下载程序

参考书籍:严蔚敏,吴伟民 《书籍结构(C语言版)》 清华大学出版社(第52页算法3.3) 书号:9787302147510

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值