數據結構之迷宮問題求解(一)利用棧與遞歸求解出口


  本文適合於對迷宮問題已有初步研究,或閱讀代碼能力較強的人.

  因此,如果你對迷宮問題一無所知,請參考其他更詳細的資料.

  迷宮問題,是一個對棧(Stack)典型應用的例子之一.

  假如,有如下10X10的迷宮(0代表通路,1代表障礙),我們需要用寫程序來找出迷宮的出口.

1 1 1 1 1 1 1 1 1 1
1 1 1 0 1 1 1 0 1 1
0 0 0 0 1 0 0 0 1 1
1 1 0 1 1 0 1 0 0 1
1 1 0 1 0 0 1 0 1 1
1 1 0 1 1 1 1 0 0 1
1 1 0 0 0 0 0 0 1 1
1 1 0 1 0 1 1 0 1 1
1 1 0 1 0 1 1 0 1 1
1 1 1 1 1 1 1 0 1 1

那么,我們可以通過兩種方式完成.

方式一:通過利用棧FILO(First In Last Out)的特性

核心代碼

/*
*函數說明:通過棧來進行迷宮求解
*參數說明:
*	Maze:迷宮地圖數組
*	  sz:迷宮大小
*      entry:迷宮入口點
*	path:用於尋找迷宮出口的棧
*返回值:找到出口返回true,沒找到返回false.
*/
bool FindMazePath(int *Maze,size_t sz,Pos &entry,stack<Pos>& path){
	//將入口壓棧
	path.push(entry);
	//如果棧不為空
	while(!path.empty()){
		//獲取棧頂元素,即上一次走的路徑
		Pos cur = path.top();
		//將其標記為已走過
		Maze[cur.x*sz+cur.y] = 3;
		//找到出口
		if(sz-1==cur.x){
			return true;
		}
		Pos next = cur;
		//下一步,向右移動
		next.x += 1;
		if(CheckIsAccess(Maze,sz,next)){
			//可以向右移動,將當前步入棧
			path.push(next);
			continue;
		}
		next = cur;
		//下一步,向左移動
		next.x -= 1;
		if(CheckIsAccess(Maze,sz,next)){
			//可以向左移動,入棧
			path.push(next);
			continue;
		}
		//下一步,向上移動
		next = cur;
		next.y += 1;
		if(CheckIsAccess(Maze,sz,next)){
			//可以向上移動
			path.push(next);
			continue;
		}
		next = cur;
		//向下移動
		next.y -= 1;
		if(CheckIsAccess(Maze,sz,next)){
			//可以向下移動
			path.push(next);
			continue;
		}
		//上、下、左、右都不能走
		path.pop();
	}
	return false;
}

方式二:通過遞歸

核心代碼

/*
*函數說明:根據遞歸尋找迷宮出口
*參數說明
*	 Maze:迷宮地圖
*	   sz:迷宮大小
*	entry:迷宮入口
*    path:用來判斷是否存在出口的棧
*返回值:無(如果存在出口,棧為空;如果不存在出口,棧中存在起點坐標)
*/
void FindMazePathR(int *Maze,size_t sz,Pos &entry,stack<Pos> & path){
	//將入口壓棧
	path.push(entry);
	Pos cur = entry;
	//將已走過的路標記為3
	Maze[cur.x*sz+cur.y] = 3;
	//找到出口,直接返回
	if(sz-1==entry.x){
		//將起點坐標彈出
		path.pop();
		return ;
	}
	Pos next = cur;
	//右
	next.x += 1;
	if(CheckIsAccess(Maze,sz,next)){
		//以當前位置為起點,遞歸進行下一步
		FindMazePathR(Maze,sz,next,path);
	}
	next = cur;
	//左
	next.x -= 1;
	if(CheckIsAccess(Maze,sz,next)){
		FindMazePathR(Maze,sz,next,path);
	}
	//上
	next = cur;
	next.y += 1;
	if(CheckIsAccess(Maze,sz,next)){
		FindMazePathR(Maze,sz,next,path);
	}
	//下
	next = cur;
	next.y -= 1;
	if(CheckIsAccess(Maze,sz,next)){
		FindMazePathR(Maze,sz,next,path);
	}
	path.pop();
}

最后,附上整個程序的完整代碼(代碼量較少,聲明與實現我就不分文件了)

迷宮問題求解完整代碼

//相關函數的聲明與實現
#ifndef __MAZE_H__
#define __MAZE_H__
#include<iostream>
#include<iomanip>
#include<stack>
#include<assert.h>
namespace Maze{
	using namespace std;
	//迷宮大小
	static const int N = 10;
	//迷宮地圖文件名
	static const char *const FILENAME = "MazeMap.txt";
	//坐標
	struct Pos{
		int x;	//橫坐標(本質是數組arr[i][j]的j)
		int y;	//縱坐標(本質是數組arr[i][j]的i)
	};
	/*
	函數說明:從文件中獲取迷宮地圖
	參數說明:
		Maze:迷宮地圖數組
		  sz:迷宮大小
	  返回值:無
	*/
	void GetMaze(int *Maze,size_t sz){
		FILE *fp = fopen(FILENAME,"r");
		//打開失敗
		if(NULL==fp){
			//輸出錯誤信息
			perror(FILENAME);
			//結束程序
			exit(1);
		}
		//將文件中的迷宮地圖讀入Maze數組內
		for(size_t i=0; i<sz; ++i){
			for(size_t j=0; j<sz;){
				//從文件流中獲取字符
				char tmp = getc(fp);
				//字符為0或為1時,導入數組
				if(tmp=='0'||tmp=='1'){
					Maze[i*sz+j]=tmp -'0';
					++j;
				}else if(EOF==tmp){
					//文件已讀完,循環還未停止
					//說明此處文件中的迷宮地圖存在問題
					assert(false);
					return ;
				}
			}
		}
		//關閉文件
		fclose(fp);
	}
	/*
	函數說明:打印迷宮
	參數說明:
		Maze:迷宮地圖數組
		  sz:迷宮大小
	  返回值:無
	*/
	void PrintMaze(int *Maze,size_t sz){
		cout<<setw(2);
		for(size_t i=0; i<sz; ++i){
			for(size_t j=0; j<sz; ++j){
				cout<<Maze[i*sz+j]<<setw(2);
			}
			cout<<endl;
		}
	}
	/*
	函數說明:檢測當前位置是否可以通過
	參數說明:
		Maze:迷宮地圖數組
			sz:迷宮大小
		   cur:當前所在位置
	返回值:可以通過返回true,不能通過返回false.
	*/
	bool CheckIsAccess(int *Maze,size_t sz,Pos cur){
		if(cur.x>=0 && cur.x<sz &&		//行坐標是否越界
			cur.y>=0 && cur.y<sz &&		//列坐標是否越界
			Maze[cur.x*sz+cur.y]==0 ){	//所在行列是否可以通過
			return true;
		}
		return false;
	}
	/*
	函數說明:通過棧來進行迷宮求解
	參數說明:
		Maze:迷宮地圖數組
			sz:迷宮大小
		entry:迷宮入口點
		path:用於尋找迷宮出口的棧
	返回值:找到出口返回true,沒找到返回false.
	*/
	bool FindMazePath(int *Maze,size_t sz,Pos &entry,stack<Pos>& path){
		//將入口壓棧
		path.push(entry);
		//如果棧不為空
		while(!path.empty()){
			//獲取棧頂元素,即上一次走的路徑
			Pos cur = path.top();
			//將其標記為已走過
			Maze[cur.x*sz+cur.y] = 3;
			//找到出口
			if(sz-1==cur.x){
				return true;
			}
			Pos next = cur;
			//下一步,向右移動
			next.x += 1;
			if(CheckIsAccess(Maze,sz,next)){
				//可以向右移動,將當前步入棧
				path.push(next);
				continue;
			}
			next = cur;
			//下一步,向左移動
			next.x -= 1;
			if(CheckIsAccess(Maze,sz,next)){
				//可以向左移動,入棧
				path.push(next);
				continue;
			}
			//下一步,向上移動
			next = cur;
			next.y += 1;
			if(CheckIsAccess(Maze,sz,next)){
				//可以向上移動
				path.push(next);
				continue;
			}
			next = cur;
			//向下移動
			next.y -= 1;
			if(CheckIsAccess(Maze,sz,next)){
				//可以向下移動
				path.push(next);
				continue;
			}
			//上、下、左、右都不能走
			path.pop();
		}
		return false;
	}
	/*
	*函數說明:根據遞歸尋找迷宮出口
	*參數說明
	*	 Maze:迷宮地圖
	*	   sz:迷宮大小
	*	entry:迷宮入口
	*    path:用來判斷是否存在出口的棧
	*返回值:無(如果存在出口,棧為空;如果不存在出口,棧中存在起點坐標)
	*/
	void FindMazePathR(int *Maze,size_t sz,Pos &entry,stack<Pos> & path){
		//將入口壓棧
		path.push(entry);
		Pos cur = entry;
		//將已走過的路標記為3
		Maze[cur.x*sz+cur.y] = 3;
		//找到出口,直接返回
		if(sz-1==entry.x){
			//將起點坐標彈出
			path.pop();
			return ;
		}
		Pos next = cur;
		//右
		next.x += 1;
		if(CheckIsAccess(Maze,sz,next)){
			//以當前位置為起點,遞歸進行下一步
			FindMazePathR(Maze,sz,next,path);
		}
		next = cur;
		//左
		next.x -= 1;
		if(CheckIsAccess(Maze,sz,next)){
			FindMazePathR(Maze,sz,next,path);
		}
		//上
		next = cur;
		next.y += 1;
		if(CheckIsAccess(Maze,sz,next)){
			FindMazePathR(Maze,sz,next,path);
		}
		//下
		next = cur;
		next.y -= 1;
		if(CheckIsAccess(Maze,sz,next)){
			FindMazePathR(Maze,sz,next,path);
		}
		path.pop();
	}
}
#endif

迷宮求解測試代碼

#include"Maze.h"
using namespace Maze;
void MazeTest(){
	int arr[N][N];		//迷宮地圖
	Pos entry = {2,0};	//起點坐標
	stack<Pos> path;	//棧
	GetMaze((int *)arr,N);	//將文件中迷宮導入到arr數組中
	PrintMaze((int *)arr,N);//打印迷宮
	FindMazePath((int *)arr,N,entry,path);//找迷宮出口
	cout<<endl<<endl;		//換行處理(使界面更整齊)
	PrintMaze((int *)arr,N);//打印走過的迷宮
}
int main(){
	MazeTest();
	return 0;
}

總結:

  1.利用棧去尋找迷宮出口,棧內最終會保存從入口到出口的所有路徑.

  2.利用遞歸去尋找迷宮出口,傳進去的棧僅僅只是用來判斷迷宮是否有出口,

  3.利用遞歸去尋找出口時,因為遞歸的特性,將會遍歷完迷宮內的所有路徑.

  最后,還有一個問題:如果一個迷宮存在多條路徑可以到達出口,那么如何得到迷宮到出口的最短路徑???

  有機會的話,我將會在下篇文章討論此事.

  附上工程文件:http://pan.baidu.com/s/1gfoNrLD


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM