【C/C++】迷宮問題詳情分析--棧的應用

引言

這是一個簡單的順序棧的應用求解迷宮問題,主要分享的是在求解這個問題的之前的準備,
分析所需的數據,獲得正確的數據結構,分析所需要的功能,劃分模塊,再分析各模塊中,需要的具體功能,以確定功能函數。
這樣也書寫代碼時,就可以事半功倍。

一,問題描述

迷宮求解問題
提出以一個m*n的長方陣表示迷宮,0和1分別表示迷宮中的通路和障礙。迷宮問題要求,求出從入口(x,y)到出口(x,y)的一條通路,或得出沒有通路的結論。
基本要求:首先實現一個以鏈表作存儲結構的棧類型,然後編寫一個求迷宮問題的非遞歸程序,求得的通路。
要求用棧實現迷宮問題的求解


將要構建的迷宮:向下爲x正方向;向右爲y正方向

在這裏插入圖片描述

二,分析所用數據結構

迷宮結構體用於存儲構建的迷宮數據。
座標結構體和棧元素結構體都是服務於棧結構體。

在這裏插入圖片描述

三、所需函數及其功能

這幅圖可以很清晰的,瞭解都有哪些函數,這些函數的功能又是什麼。
圖中很多函數都是爲了一個函數服務的,即求解迷宮的函數。
藍色底的函數,實現了但是沒有測試,大家可以自行測試

在這裏插入圖片描述

四、程序執行詳細框圖

這是整個迷宮問題項目的詳細執行過程。大家可以先看看,到時候閱讀代碼也會更加清洗直觀。
不同的顏色,是一個不同的模塊,實現相應的功能

在這裏插入圖片描述

五、代碼實現-詳細註釋

代碼相應的地方都有註釋,也體現了我思考的過程,如有錯誤或者更優解,歡迎在指正討論。

1、maze.h

#ifndef __MAZE_H__    
#define __MAZE_H__

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

#define TRUE 1
#define FALSE 0

#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10

#define COLUMN 10  //列
#define ROW 10	   //行

typedef struct{
   
   
	char** maze;		//迷宮二維數組
	int** footprint;	//足跡二維數組
	int row;
	int column;
}MazeType;

typedef struct{
   
   
	int x;
	int y;
}PosType;

typedef struct{
   
   
	int ord;			//通道塊在路徑上的序號
	PosType seat;		//通道塊在迷宮中的“座標位置”
	int di;				//從此通道塊走向下一個通道塊的“方向”
}SElemType;

typedef struct{
   
   
	SElemType* base;
	SElemType* top;
	int stacksize;
}SqStack;

//構造一個空棧
bool InitStack(SqStack* S);
//初始化迷宮數據
bool InitMaze(MazeType* M);
//判斷是否爲空棧
bool IsStackEmpty(SqStack S);
//入棧,元素e爲新的棧頂元素,傳入e形參拷貝值,返回改變的棧S,及是否入棧成功
bool Push(SqStack* S, SElemType e);
//出棧,指針傳入地址,直接改變e變量,即返回改變的e和棧S,及是否出棧成功
bool Pop(SqStack* S, SElemType* e);
//輸出迷宮
bool PrintfMaze(MazeType* M);
//輸出迷宮的路徑
bool PrintfFoot(MazeType* M, SqStack* S);
//將迷宮的當前位置Pos設置爲“走過”,即footprint該位置爲1
bool FootPrint(MazeType* M, PosType pos);
//判斷當前位置是否走過
bool Pass(MazeType* M, PosType pos);
//創建新的節點,用step,pos,d初始化該點
SElemType NewSElemType(int step, PosType pos, int d);
//將位置pos的方向設爲d
PosType NextPos(PosType pos, int d);
//若迷宮maze中存在從入口start到出口end的通道,則求得一條存放在棧中(從棧底到棧頂)
bool MazePath(SqStack* S, MazeType maze, PosType start, PosType end);


//清空棧
bool ClearStack(SqStack* S);
//從棧底到棧頂依次對每個元素進行訪問
bool StackTravel(const SqStack* S);
//返回棧的長度,即S元素的個數
int StackLength(SqStack S);
//若棧不爲空,則用e返回S的棧頂元素
bool GetTop(SqStack S, SElemType* e);


#endif 

2、maze.c

#include "maze.h"

//構造一個空棧
bool InitStack(SqStack* S)
{
   
   
	//100*SElemType
	S->base = (SElemType*)malloc(STACK_INIT_SIZE*sizeof(SElemType));	
	if(!S->base)
	{
   
   	
		printf("申請空間失敗,迷宮無法初始化.\n");
		return false;
	}

	S->top = S->base;
	S->stacksize = STACK_INIT_SIZE;
	return true;
}

//初始化迷宮數據
bool InitMaze(MazeType* M)
{
   
   
	char mz[ROW][COLUMN]={
   
   	
	{
   
   '#',' ','#','#','#','#','#','#','#','#'},
	{
   
   '#',' ',' ','#',' ',' ',' ','#',' ','#'},
	{
   
   '#',' ',' ','#',' ',' ',' ','#',' ','#'},
	{
   
   '#',' ',' ',' ',' ','#','#',' ',' ','#'},
	{
   
   '#',' ','#','#','#',' ',' ',' ',' ','#'},
	{
   
   '#',' ',' ',' ','#',' ','#',' ','#','#'},
	{
   
   '#',' ','#',' ',' ',' ','#',' ',' ','#'},
	{
   
   '#',' ','#','#','#',' ','#','#',' ','#'},
	{
   
   '#','#',' ',' ',' ',' ',' ',' ',' ',' '},
	{
   
   '#','#','#','#','#','#','#','#','#','#'},
	};

	M->maze = (char **)malloc(sizeof(char*)*ROW);		//相當於分配一維數組空間,10個char*變量空間
	M->footprint = (int **)malloc(sizeof(int*)*ROW);	//相當於分配一維數組空間,10個int*變量空間
	
	if(!M->maze || !M->footprint)
	{
   
   
		printf("申請空間失敗,迷宮無法初始化.\n");
		return false;
	}

	for(int i = 0; i < ROW; i++)
	{
   
   
		M->maze[i]=(char*)malloc(sizeof(char)*COLUMN);		//相當於分配二維數組空間,每個個char*指向,10個char大小變量空間
		M->footprint[i]=(int*)malloc(sizeof(int)*COLUMN);	//相當於分配二維數組空間,每個個int*指向,10個int大小變量空間
		if(!M->maze[i] || !M->footprint[i])
		{
   
   
			printf("申請空間失敗,迷宮無法初始化.\n");
			return false;
		}
	}
	for(int i = 0; i <ROW; i++)
	{
   
   
		for(int j = 0; j < COLUMN; j++)
		{
   
   
			M->maze[i][j] = mz[i][j];
			M->footprint[i][j] = 0;
		}
	}
	M->row = ROW;
	M->column = COLUMN;
	return true;
}

//判斷是否爲空賬
bool IsStackEmpty(SqStack S)
{
   
   
	if(S.top == S.base)
		return true;
	else
		return false;
}

//入棧,元素e爲新的棧頂元素,傳入e形參拷貝值,返回改變的棧S,及是否入棧成功
bool Push(SqStack* S, SElemType e)
{
   
   
	//結構體類型,按單位大小相減類比int型,每個int型爲4byte,相減2-1也是按斯單位相減
	if(S->top - S->base >= S->stacksize)	//如果超出本來的長度,進行動態的添加,每一次添加10個SElemType大小空間,STACKINCREMENT=10
	{
   
   
		S->base = (SElemType*)realloc(S->base,(S->stacksize + STACKINCREMENT)*sizeof(SElemType));
		if(!S->base);
		{
   
   
			printf("重新申請空間失敗.\n");
			return false;
		}
		S->top = S->base + S->stacksize;	//棧頂指針指向原先棧的尾部,棧底+棧長度
		S->stacksize +=STACKINCREMENT;		//棧的長度+10
	}
	*S->top++=e;	//棧頂指針+1前進,並且e賦值給解引用的指針,入棧
	//後置++/--爲第一優先級,*和前置++/--爲第二優先級
	return true;
}

//出棧,指針傳入地址,直接改變e變量,即返回改變的e和棧S,及是否出棧成功
bool Pop(SqStack* S, SElemType* e)
{
   
   
	if(S->top == S->base)
	{
   
   
		printf("棧爲空.\n");
		return false;
	}
	*e = *(--S->top);	//棧頂指針-1返回,並且解引用賦值給e
	return true;
}

//輸出迷宮
bool PrintfMaze(MazeType* M)
{
   
   
	printf("%s","xy");
	for(int i=0;i<M->column;i++)
	{
   
   
		printf("%d",i);
	}
	printf("\n");
	
	for(int i=0; i<M->row; i++)
	{
   
   
		printf("%d ",i);
		for(int j=0; j<M->column; j++)
		{
   
   
			printf("%c",M->maze[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	
	//footprintf
	printf("%s","xy");
	for(int i=0;i<M->column;i++)
	{
   
   
		printf("%d",i);
	}
	printf("\n");
	for(int i=0; i<M->row; i++)
	{
   
   
		printf("%d ",i);
		for(int j=0; j<M->column; j++)
		{
   
   
			printf("%d",M->footprint[i][j]);
		}
		printf("\n");
	}
	printf("\n");

	return true;
}

//輸出迷宮路徑
bool PrintfFoot(MazeType* M, SqStack* S)
{
   
   
	SElemType* p;
	for(int i=0; i<M->row; i++)		//將footprint置0
	{
   
   
		for(int j=0; j<M->column; j++)
		{
   
   
			M->footprint[i][j]=0;
		}
	}

	p = S->base;
	if(S->base == S->top)
	{
   
   
		printf("棧爲空.\n");
		return false;
	}
	while(p != S->top)	//根據棧中存有的節點的座標,對footprint進行1路徑賦值
	{
   
   
		M->footprint[p->seat.x][p->seat.y] = 1;
		*p++;
	}
	for(int i=0; i<M->row; i++)	//輸出路徑
	{
   
   
		for(int j=0; j<M->column; j++)
		{
   
   
			printf("%d",M->footprint[i][j]);
		}
		printf("\n");
	}
	return true;
}

//將迷宮的當前位置Pos設置爲“走過”,即footprint該位置爲1
bool FootPrint(MazeType* M, PosType pos)	//FootPrint足跡
{
   
   
	if((pos.x>M->row) || (pos.y>M->column))	
	{
   
   
		printf("座標越界.\n");
		return false;
	}
	M->footprint[pos.x][pos.y]=1;
	return true;
}

//判斷當前位置是否走過
bool Pass(MazeType* M, PosType pos)
{
   
   
	if((pos.x > M->row) || (pos.y > M->column))	
	{
   
   
		printf("座標越界.\n");
		return false;
	}
	
	if((0 == M->footprint[pos.x][pos.y])&&(M->maze[pos.x][pos.y]==' '))
		return true;	//通路沒走過
	else
		return false;	//通路走過或者牆
}

//創建新的節點,用step,pos,d初始化該點
SElemType NewSElemType(int step, PosType pos, int d)
{
   
   
	SElemType e;
	e.ord = step;
	e.seat = pos;
	e.di = d;
	return e;

}

//將位置pos的方向設爲d
PosType NextPos(PosType pos, int d)
{
   
   
	switch(d)
	{
   
   
		case 1:	//向下
			pos.x++;
			break;
		case 2: //向右
			pos.y++;
			break;
		case 3: //向上
			pos.x--;
			break;
		case 4: //向左
			pos.y--;
			break;
		default:
			printf("error.\n");
	}
	return pos;
}

//若迷宮maze中存在從入口start到出口end的通道,則求得一條存放在棧中(從棧底到棧頂)
bool MazePath(SqStack* S, MazeType maze, PosType start, PosType end)
{
   
   
	int curstep = 1;
	SElemType e;
	PosType curpos = start;
	InitStack(S);
	do
	{
   
   
		if(true == Pass(&maze, curpos)) //通路沒走過
		{
   
   
			FootPrint(&maze,curpos);將當前點標記爲走過
			e=NewSElemType(curstep,curpos,1);//創建棧元素,將當前點的信息存儲,便於出入棧及改變當前點的di信息
			Push(S,e);
			if((curpos.x==end.x)&&(curpos.y==end.y))//終點
			{
   
   
				printf("迷宮路徑:\n");
				PrintfFoot(&maze,S);//打印通路路徑
				return true;
			}
			curpos = NextPos(curpos,1);//向當前點的di方向偏移 ,即下一個點
			curstep++;//步數+1
		}
		else //通路走過或者牆
		{
   
   
			if(!IsStackEmpty(*S))
			{
   
   
				Pop(S,&e);
				while(e.di==4 && !IsStackEmpty(*S)) //四個方向遍歷完,說明此點不是正確道路,,且且棧不爲空,就一直出棧
				{
   
   
					Pop(S,&e);
				}
				if(e.di<4)
				{
   
   
					e.di++;//改變當前點的di,偏移方向
					Push(S,e);//將改變了di的當前點信息,入棧
					curpos=NextPos(e.seat,e.di);//向當前點的di方向偏移
				}
			}
		}
	}while(!IsStackEmpty(*S));
	
	return false;
}

//清空棧
bool ClearStack(SqStack* S)
{
   
   
	//把棧S置爲空棧
	if(!S) return false;
	S->top = S->base;
	return true;
}

//從棧底到棧頂依次對每個元素進行訪問
bool StackTravel(const SqStack* S)
{
   
   
	SElemType* p = S->base;
	if(S->base == S->top)
	{
   
   
		printf("棧爲空.\n");
		return false;
	}
	printf("棧中元素:\n");
	while(p != S->top)
	{
   
   
		printf("x=%d,y=%d\n",p->seat.x,p->seat.y);
		*p++;
	}
	printf("\n");
	return true;
}

//返回棧的長度,即S元素的個數
int StackLength(SqStack S)
{
   
   
	return S.stacksize;
}

//若棧不爲空,則用e返回S的棧頂元素
bool GetTop(SqStack S, SElemType* e)
{
   
   
	if(S.top == S.base)
	{
   
   
		printf("棧爲空.\n");
		return false;
	}
	else
	{
   
   
		*e = *(S.top - 1);
		//printf("棧頂元素:%c\n",*e);
		return true;
	}
}

3、maze.c

#include "maze.h"
int main()
{
   
   
	MazeType maze;
	SqStack *stack =(SqStack *)malloc(sizeof(SqStack));
	PosType start,end;
	start.x = 1;
	start.y = 6;
	end.x = 8;
	end.y = 9;

	InitMaze(&maze);
	
	printf("maze:\n");
	PrintfMaze(&maze);

	if(true == MazePath(stack,maze,start,end))
		printf("maze can out.\n");
	else
		printf("maze can not out.\n");
		
	StackTravel(stack);
	printf("棧的長度:%d\n",StackLength(*stack));
	
	SElemType ele;
	GetTop(*stack,&ele);
	printf("棧頂元素:ord=%d,x=%d,y=%d,di=%d\n",ele.ord,ele.seat.x,ele.seat.y,ele.di);

	ClearStack(stack);
	GetTop(*stack,&ele);
	
	//銷燬棧——銷燬free空間,需要在同一個內存空間銷燬
	free(stack);
	stack=NULL;
	
	return 0;
}

六,效果展示

此測試起點(1,6),終點(8,9)

在這裏插入圖片描述

相信大家看到這個結果是會有疑問的,因爲雖然得到一條正確的迷宮路徑,但卻不是最優解,這是爲什麼呢?

依舊存在缺陷的迷宮算法,無法獲得最優路徑解,因爲遍歷的方向固定,所以大家有什麼辦法可以優化它呢?

   

如有不足之處,還望指正 1


  1. 如果對您有幫助可以點贊、收藏、關注,將會是我最大的動力 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章