1. 程式人生 > >資料結構::迷宮(一)--棧的一個應用

資料結構::迷宮(一)--棧的一個應用

【前情描述】:我們先來看一張圖片:

  

(在這張圖片裡我們用“1”來表示牆,用“0”來表示通路。紅色方塊表示入口點,綠色方塊表示出路)

 我們要從迷宮的出口開始走找出路,即紅色走到綠色,那麼怎麼解決這個問題呢?彆著急,往下看,聽我細細講解:

【解決迷宮問題】:

方法一:利用棧來解決

思路分析:

1)我們先要給定一個入口點,從入口點開始出發,即先將入口點進行壓棧

2)我們用一個節點來表示棧頂的元素,用此節點來跟蹤我們要走的足跡,每當我們走一步,就將節點進行壓棧

3)但是我們走的時候有四個方向可以走,所以每走一步就要對這四個方向進行判定,是否可以走通

4)當四個方向都可以走通的時候,就重複以上步驟,當四個方向都走不通的時候,我們就出棧,回溯

5)什麼時候結束,當棧為空的時候說明沒有通路,回溯到最初的入口點

6)什麼時候我們找到了一條通路,就是當我們走的路的行等於迷宮的行的時候

   接下來,我們直接看程式碼:

#include<iostream>
#include<assert.h>
#include<stack>
using namespace std;
//#define N 10
const size_t N = 10;      //儘量使用const

//迷宮中所走的位置表示
struct Pos
{
    int _row;  //所在位置的行
    int _col;  //所在位置的列
};

//獲取迷宮
void GetMaze(int* maze,size_t n)
{
	FILE* fp = fopen("1.txt","r");  //先通過相對路徑開啟儲存迷宮地圖的文字
	assert(fp);       //開啟文字是否成功
	for(size_t i = 0; i<n; i++)
	{
		for(size_t j = 0; j<n;)
		{
			int ret = fgetc(fp);
			if((ret == '0')|| (ret == '1'))
			{
				maze[i*n+j] = ret-'0';      //注意開啟的是文字要轉化成讀出的二進位制形式
				j++;
			}
			//如果迷宮地圖本身不是n*n的,那麼上面的迴圈就會成為死迴圈
			if(ret == EOF)
			{
				cout<<"出錯!"<<endl;
			}
		}
	}
}
//檢查此位置是否可以通
bool CheckIsAccess(int* maze,size_t n,Pos pos)
{
	//首先這個位置得合法,其次再是pos這個位置是否為0
	if(pos._row>=0 && pos._row<n &&
		pos._col>=0 && pos._col<n &&
		maze[pos._row*n+pos._col] == 0)
	{
		return true;
	}
	return false;
}
//求解迷宮路徑
bool GetMazePath(int* maze,size_t n,Pos entry,stack<Pos>& path)
{
	assert(maze);
	path.push(entry);
	Pos cur;    
	Pos next;
	while(!path.empty())  //當棧不為空的時候,才能進行路徑的尋找
	{
		cur = path.top(); 
		maze[cur._row*n+cur._col] = 2;   //把走過的這個位置進行標記
		next = cur;  //在每次進行判斷之後,要將next進行重置    
		if(next._row == n-1)
		{
			return true;   //找到了一條通路
		}
		//開始對上下左右進行探測
		//對上的探測
		next._row -= 1;
		//檢查路是否可以通
		if(CheckIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//對下的探測
		next = cur;
		next._row += 1;
		//檢查路是否可以通
		if(CheckIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//對左的探測
		next = cur;
		next._col -= 1;
		//檢查路是否可以通
		if(CheckIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//對右的探測
		next = cur;
		next._col += 1;
		//檢查路是否可以通
		if(CheckIsAccess(maze,n,next))
		{
			path.push(next);
			continue;
		}
		//到這個位置,說明四個方向都走不通,進行回溯
		path.pop();    //直接出棧,即前一個位置,再次上去進行探測
	}

	return false;  //棧為空,沒有找到通路     
}


//列印迷宮
void Print(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;
	}
}
int main()
{
	int maze[N][N];
	stack<Pos> path;
	Pos entry = {2,0};
	//輸出迷宮
	GetMaze((int*)maze,N);
	cout<<"是否找到迷宮:"<<GetMazePath((int*)maze,N,entry,path)<<endl;
	Print((int*) maze,N);
	return 0;
}

方法二:利用遞迴來解決

思路分析:遞迴就是將一個問題不斷劃分成若干個子問題

1)對於這道題,我們依然從入口點出發,開始從入口點判斷,進行四個方向的探測

2)接著我們將要走的下一個位置作為入口點,進行探測,這樣不斷的遞迴

3)當我們沒有找到通路的時候,開始回變數溯遞迴,這裡我們依然不將壓棧的節點不刪除,即(path),用棧來記錄遞迴的路徑

    我們來看程式碼:

#include<iostream>
#include<assert.h>
#include<stack>
using namespace std;
#define N 10
//const size_t N = 10;

//迷宮中所走的位置表示
struct Pos
{
   /* size_t*/ int _row;  //所在位置的行
	/*sizt_t*/int _col;  //所在位置的列
};

//獲取迷宮
void GetMaze(int* maze,size_t n)
{
	FILE* fp = fopen("2.txt","r");  //先通過相對路徑開啟儲存迷宮地圖的文字
	assert(fp);       //開啟文字是否成功
	for(size_t i = 0; i<n; i++)
	{
		for(size_t j = 0; j<n;)
		{
			int ret = fgetc(fp);
			if((ret == '0')|| (ret == '1'))
			{
				maze[i*n+j] = ret-'0';      //注意開啟的是文字要轉化成讀出的二進位制形式
				j++;
			}
			//如果迷宮地圖本身不是n*n的,那麼上面的迴圈就會成為死迴圈
			if(ret == EOF)
			{
				cout<<"出錯!"<<endl;
			}
		}
	}
}
//檢查此位置是否可以通
bool CheckIsAccess(int* maze,size_t n,Pos pos)
{
	//首先這個位置得合法,其次再是pos這個位置是否為0
	if(pos._row>=0 && pos._row<n &&
		pos._col>=0 && pos._col<n &&
		maze[pos._row*n+pos._col] == 0)
	{
		return true;
	}
	return false;
}
//求解迷宮路徑
void GetMazePath_r(int* maze,size_t n,Pos entry,stack<Pos>& path)
{
	assert(maze);
	path.push(entry);
	Pos cur;    
	Pos next;
    cur = entry;
	maze[cur._row*n+cur._col] = 2;   //把走過的這個位置進行標記
	next = cur;
	if(next._row == n-1)
	{
		return ;   //找到了一條通路
	}
	//開始對上下左右進行探測
	//對上的探測
	next = cur;
	next._row -= 1;
	//檢查路是否可以通
	if(CheckIsAccess(maze,n,next))
	{
		GetMazePath_r(maze,n,next,path);
	}
	//對下的探測
	next = cur;
	next._row += 1;
	//檢查路是否可以通
	if(CheckIsAccess(maze,n,next))
	{
		GetMazePath_r(maze,n,next,path);
	}
	//對左的探測
	next = cur;
	next._col -= 1;
	//檢查路是否可以通
	if(CheckIsAccess(maze,n,next))
	{
		GetMazePath_r(maze,n,next,path);
	}
	//對右的探測
	next = cur;
	next._col += 1;
	//檢查路是否可以通
	if(CheckIsAccess(maze,n,next))
	{
		GetMazePath_r(maze,n,next,path);
	}
	//到這個位置,說明四個方向都走不通,進行回溯
	path.pop();  
}   


//列印迷宮
void Print(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;
	}
}
int main()
{
	int maze[N][N];
	stack<Pos> path;
	Pos entry = {2,1};
	//輸出迷宮
	GetMaze((int*)maze,N);
	GetMazePath_r((int*) maze,N,entry,path);
	Print((int*) maze,N);
    cout<<"是否找到迷宮:"<<!path.empty()<<endl;
	return 0;
}
到這裡,相信聰明的你對迷宮問題一定理解了,對棧的應用又有了更深層次的掌握。吐舌頭

【注意】:(關於本文章有些要說明的)

1)我在實現迷宮的時候,首先是構建了二維陣列,將上述的迷宮儲存下來,但是我實現的時候利用一維陣列進行實現。(二維陣列實際上也是一維陣列)

2)我是將迷宮存在了當前工程下,用相對路徑來讀取迷宮地圖

*讀者可以自己用二維陣列實現一把,讀取迷宮可以採取其他的方式

*這裡最重要的是理解了走迷宮的思想,所以實現方法可以多種多樣嘛大笑