1. 程式人生 > >關於棧與遞迴求解迷宮與迷宮最短路徑問題

關於棧與遞迴求解迷宮與迷宮最短路徑問題

一、棧實現迷宮問題:

問題描述:用一個二維陣列模擬迷宮,其中1為牆,0為通路,用棧方法判斷迷宮是否有出口,下圖為簡單模擬的迷宮:


思想:

1.首先給出入口點,如上圖入口點座標為{2,0};

2.從入口點出發,在其上下左右四個方向試探,若為通路(值為0)時,則向前走,並將每次通路結點入棧(push)儲存和標記走過的路為2;

3.當四個方向都沒有通路時,則開始回溯,將棧中儲存的結點開始pop,並將每個pop位置標記為3,直到在一個位置重新找到新的路,若沒有新路,棧最終將為空,即迷宮沒有出口;

4.當棧不為空,並且如在上圖例子中找到位置{9,2},即其橫座標=行數-1時,則找到迷宮出口,此時棧中儲存著這條通路路徑。

程式碼實現:

#include <iostream>
#include <cstdlib>
#include <assert.h>
#include <stack>
using namespace std;

const size_t N=10;
struct Pos
{
	int _row;
	int _col;
};

bool CheakIsAccess(int *maze,size_t n,Pos pos)  //判斷每走一次是否為通路(棧)
{
	if((maze[pos._row*n+pos._col]==0)&&(pos._row>=0&&pos._row<n)&&(pos._col>=0&&pos._col<n))
	{
		return true;
	}
	return false;
}

bool GetMazePath(int *maze,size_t n,Pos entry,stack<Pos> &path)  //棧方法判斷迷宮是否有出口
{
	assert(maze);
	Pos cur=entry; //cur儲存當前位置
	path.push(cur);
	Pos next=cur;  //next儲存下一個位置
	while(!path.empty())
	{
		cur=path.top();
		maze[cur._row*n+cur._col]=2;  //標記走過的路
		if(cur._row==n-1)   //找到一條通路
		{
			return true;
		}
		//試探法:上下左右判斷是否有通路
		//上
		next=cur;
		next._row-=1;
		if(CheakIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//右
		next=cur;
		next._col+=1;
		if(CheakIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//下
		next=cur;
		next._row+=1;
		if(CheakIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//左
		next=cur;
		next._col-=1;
		if(CheakIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		maze[cur._row*n+cur._col]=3;  //若四個方向都沒有通路為死路時標記為3
		path.pop();     //並且回溯
	}
	return false;
}

void GetMaze(int *maze,size_t N)   //獲取迷宮
{
	FILE *fout=fopen("MazeMap.txt","r");  //在檔案中讀取
	assert(fout);
	for(size_t i=0;i<N;++i)
	{
		for(size_t j=0;j<N;)
		{
			int value=fgetc(fout);
			if(value=='1'||value=='0')
			{
				maze[i*N+j]=value-'0';
				++j;
			}
			else if(value==EOF)
			{
				cout<<"Error(maze)"<<endl;
				fclose(fout);
				return;
			}
		}
	}
	fclose(fout);
}

void PrintMaze(int *maze,size_t N)  //列印迷宮
{
	for(size_t i=0;i<N;++i)
	{
		for(size_t j=0;j<N;++j)
		{
			cout<<maze[i*N+j]<<" ";
		}
		cout<<endl;
	}
}

void TestMaze()
{
	int maze[N][N];
	GetMaze((int*)maze,N);
	PrintMaze((int*)maze,N);  //列印迷宮
	cout<<endl;

	stack<Pos> path;
	Pos entry={2,0};   //入口
	maze[entry._row][entry._col]=2;//標記入口先為2
    //棧尋找迷宮是否有出口
	GetMazePath((int*)maze,N,entry,path);
	PrintMaze((int*)maze,N);
	cout<<"迷宮是否有通路?"<<!path.empty()<<endl;
}

int main()
{
	TestMaze();
	system("pause");
	return 0;
}
結果顯示:



棧中儲存路徑:


二、遞迴實現迷宮最短路徑問題

問題描述:迷宮中有幾條通路路徑,用遞迴求解迷宮中的最短路徑,如下迷宮圖:


思想:

1.找到入口點,上下左右試探,試探時若下一個位置為0或者若下一個位置值大於原位置值加1,則表示為一個通路,若找到通路則遞迴呼叫自身,每走一次標記其值為前一個值加1,並將每個位置入棧儲存;


2.若找到通路將此時原棧中儲存的路徑儲存到另一個空棧ShortPath中,原棧則pop,並且遞迴結束返回;


3.如上圖返回到位置{6,7},其左邊為0為通路,在開始遞迴呼叫,呼叫到{6,6},值變為18,其左邊位置為0通路,呼叫到{6,5},值變為19,下一個位置值都不大於原位置值加1,在此遞迴結束原路返回;

4.再返回到位置{6,4},其右邊位置值大於{6,4}位置值加1,為通路,在此開始遞迴呼叫,每次下一個位置值都大於原位置值加1,再此找到第二條通路;


5.將此時棧與ShortPath的個數size比較,若小於,則在將此時棧內容賦於ShortPath中;


6.原棧再次pop,遞迴又一次結束原路返回,最終到入口點,原棧為空,ShortPath儲存路徑為迷宮最短路徑。


程式碼實現:

bool CheakIsAccess(int *maze,size_t n,Pos cur,Pos next)  //判斷每走一次是否為通路(遞迴)
{
	if((maze[next._row*n+next._col]==1)||(next._row<0||next._row>=n)||(next._col<0||next._col>=n))
	{
		return false;
	}
	if(maze[next._row*n+next._col]==0)
	{
		return true;
	}
	if(maze[next._row*n+next._col]>maze[cur._row*n+cur._col]+1)//若next位置值大於cur位置值加1則表示為一個新路
	{
		return true;
	}
	return false;
}

void GetMazePathR(int *maze,size_t n,Pos entry,stack<Pos> &path,stack<Pos> &ShortPath)  //遞迴方法求解最短路徑
{
	assert(maze);
	Pos cur=entry;
	path.push(cur);
	Pos next=cur;
	if(cur._row==n-1)
	{
		if(ShortPath.empty()||path.size()<ShortPath.size())  //將最短路徑給ShorytPath棧
		{   

			ShortPath=path;
		}
		path.pop();
		return;
	}	
	//上
	next=cur;
	next._row-=1;
	if(CheakIsAccess(maze,n,cur,next))
	{
		maze[next._row*n+next._col]=maze[cur._row*n+cur._col]+1;  //每走一次將現值標記為前一個值加1
		GetMazePathR((int*)maze,N,next,path,ShortPath);	
	}
	//右
	next=cur;
	next._col+=1;
	if(CheakIsAccess(maze,n,cur,next))
	{
		maze[next._row*n+next._col]=maze[cur._row*n+cur._col]+1;
		GetMazePathR((int*)maze,N,next,path,ShortPath);
	}
	//下
	next=cur;
	next._row+=1;
	if(CheakIsAccess(maze,n,cur,next))
	{
		maze[next._row*n+next._col]=maze[cur._row*n+cur._col]+1;
		GetMazePathR((int*)maze,N,next,path,ShortPath);
	}
	//左
	next=cur;
	next._col-=1;
	if(CheakIsAccess(maze,n,cur,next))
	{
		maze[next._row*n+next._col]=maze[cur._row*n+cur._col]+1;
		GetMazePathR((int*)maze,N,entry,path,ShortPath);
	}
	path.pop();
}

void TestMaze()
{
	int maze[N][N];
	GetMaze((int*)maze,N);
	PrintMaze((int*)maze,N);  //列印迷宮
	cout<<endl;

	stack<Pos> path;
	stack<Pos> ShortPath; //(遞迴實現中)儲存棧中最短路徑
	Pos entry={2,0};   //入口
	maze[entry._row][entry._col]=2;//標記入口先為2
	//遞迴尋找迷宮最短路徑
    GetMazePathR((int*)maze,N,entry,path,ShortPath); 
    PrintMaze((int*)maze,N);
    cout<<"迷宮是否有通路?"<<!ShortPath.empty()<<endl;
}