棧、隊列及其應用---迷宮問題

一、需求分析

 

1、輸入的形式和輸入值的範圍

本程序需要三組輸入,分別是迷宮的行數、列數,最後把迷宮的值作爲一個二維數組進行輸入,根據提示輸入起點座標(sx,sy)和終點座標(ex,ey),輸入的類型均是正整數。

 

2、輸出的形式

求得的通路以三元組(i,j,d)的形式輸出,其中:(i,j)指示迷宮中的一個座標,d表示走到下一座標的方向。結果顯示爲迷宮的一條通路(i1,j1,d1),(i2,j2,d2),(i3,j2,d2)..如果沒有找到通路,則會輸出顯示“No Way ”

3、程序所能達到的功能

以一個m×n的長方陣表示迷宮,0和1分別表示迷宮中的通路和障礙。設計一個程序,對任意設定的迷宮,求出一條從入口到出口的通路,或得到沒有通路的結論。

 

4、測試數據

1)正確數據

 ①輸入迷宮的行數m=9,列數n=8,構建迷宮


輸入的起點1 1 輸入終點 9 8

正確的輸出結果應該爲:

(1,1,1),(1,2,2),(2,2,2),(3,2,3),(3,1,2),(4,1,2),(5,1,1),(5,2,1),(5,3,2),(6,3,1),

(6,4,1),(6,5,4),(5,5,1),(5,6,1),(5,7,2),(6,7,2),(7,7,2),(8,7,2),(9,7,1)

② 輸入迷宮的行數m=5,列數n=6;構建迷宮


輸入起點1 1 輸入終點 5 4

正確的輸出結果應該爲:

(1,1,2),(2,1,2),(3,1,1),(3,2,2),(4,2,1),(4,3,2),(5,3,1)

 

③沒有通路的情況

輸入迷宮的行數m=5,列數n=6,構建迷宮

輸入起點1 1 輸入終點5 6

結果顯示:”No Way!”

2)錯誤數據

①輸入迷宮的行數m=9,列數n=8  構建迷宮


輸入起點9 9 (錯誤數據 超出範圍)

結果顯示“起點輸出範圍錯誤,請重新輸入”

輸入起點1 1

輸入終點9 9(錯誤數據,超出範圍)

結果顯示“終點輸入範圍錯誤,請重新輸入”

輸入終點9 8

最後結果成功顯示:

(1,1,1),(1,2,2),(2,2,2),(3,2,3),(3,1,2),(4,1,2),(5,1,1),(5,2,1),(5,3,2),(6,3,1),

(6,4,1),(6,5,4),(5,5,1),(5,6,1),(5,7,2),(6,7,2),(7,7,2),(8,7,2),(9,7,1)

1、設計思路及方案

設計一個二維數組image[][]來存儲迷宮的值,其中”1”代表圍牆,不可通過,0”代表通路,可以通過,設計二維數組dir[][]存儲方向,vis[][]做標記,如果訪問過,就把當前腳標下的值改成1存進visit

通過定義棧的結構類型存儲可以通過的路徑,並用深度優先搜索的方法遍歷迷宮尋找通路,如果不通則進行回溯,並更改方向。

 

二、概要設計

1、抽象數據類型的定義

typedef struct node{         //node  結構體(x,y,direction) 
	int x,y,direction;
}ElementType;
/*定義node 結構體,存儲橫座標:x,縱座標:y 方向:direction */
typedef struct SNode{
	ElementType data;
	SNode* next;
} SNode, *LinkStack;
/*定義SNode 結構體爲棧類型,data是node類型的數據,next 爲指向當前棧節點下一節點的指針,指向整個棧的爲指針類型 *LinkStack*/

2、用到的基本操作

void InitLinkStack(LinkStack&L);            //初始化鏈棧L
void Push(LinkStack&L,ElementType e);       //元素e 入棧L
void Pop(LinkStack&L);                     //L棧頂元素出棧
bool EmptyLinkStack(LinkStack L);           //判斷棧是否爲空
ElementType GetTop(LinkStack L);           //獲取棧頂元素
void Init();                               //初始化迷宮
void PutIn();                  //迷宮起點和終點的輸入輸出操作
void Print();                   //打印路徑
void Deal();                //探索路徑的和儲存路徑的處理函數

3、主程序流程

Int main(){

初始化path;

創建迷宮地圖;

起點終點輸入操作;

探索路徑並存儲打印路徑,最後輸出結果;

Return 0;

}

4、函數調用關係圖


三、詳細設計

1、主函數模塊代碼

程序解釋:path是之前聲明過的用來存儲路徑的棧,這裏對其進行初始化,然後輸入迷宮信息,和起點、終點的位置,最後處理函數探索路徑並輸出結果。

2、棧的初始化模塊

程序解釋:以鏈棧L的引用作爲形參,初始化即將其next指針置爲NULL

3、入棧操作模塊


程序解釋:本函數接收一個LinkStack 類型的L的引用作爲要操作的棧對象,將元素e入棧,用帶頭結點的單鏈表的頭插法操作完成。

4、出棧操作模塊

程序解釋:接收一個LinkStack 引用類型的形參LL棧頂的元素(最新的元素)彈出棧。

5、判空操作模塊

程序解釋:函數接收一個LinkStack 類型的形參L,返回值是bool型,如果判斷該棧爲空,返回true,如果不爲空,返回false

6、獲取棧頂元素的模塊

程序解釋:函數接收一個LinkStack類型的形參L,返回值爲ElementType 類型,如果L不爲空,則返回L的棧頂元素。

7、創建迷宮模塊

程序解釋:利用兩層for循環完成對二維數組的賦值,其中n是要輸入的行數,m是要輸入的列數,ij1開始,說明二維數組的第一個元素是image[1][1]而不是image[0][0],這樣寫是爲了後面的操作更容易和直觀些。

8、輸入起點和終點的模塊

程序解釋:輸入起點(sx,sy)和終點(sx,sy)的值並對它們的值進行範圍內的判斷,如果不符合範圍,就重新輸入。

9、打印路徑的模塊

程序解釋:首先創建並初始化一個空棧p,由於我們存進path的那些路徑是先進後出的,如果直接打印path 的話,只能打印出一條逆序列,所以在這裏借用棧p,path頂部的元素彈出來存到棧p的底部,直到棧path爲空,這樣實現了內部存儲的數據順序顛倒過來,通過for循環輸出一條完整的通路。

10、對路徑的探索和處理模塊

程序解釋: 使用深度優先搜索的方式遍歷整個迷宮,每到一個節點就看其是否能通過,然後從該節點向四個方向探索,如果能通過,就將其入棧,並給它打上訪問過的標記,取得棧頂元素繼續對四個方向進行探索,直至找到出口,對路徑進行打印,如果找不到出口,則顯示“No Way!(方向: 1:""  2:"" 3:"" 4:""

四、測試結果

①路徑存在


②路徑存在


③路徑不存在

(2)錯誤數據

附錄(完整代碼)


#include<iostream>
using namespace std;
int image[20][20];       //定義迷宮 
int vis[20][20];                       
    //存儲走過的路徑  
    //用二維數組代表四個方向 行數: 1:"右"  2:"下 " 3:"左" 4:"上"
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};     
int n,m;
int sx,sy,ex,ey;                //  迷宮起點:(sx,sy)   迷宮終點(ex,ey)
typedef struct node{            //node  結構體(x,y,direction) 
int x,y,direction;
}ElementType;

typedef struct SNode{
	ElementType data;
	SNode* next;
} SNode, *LinkStack;
  LinkStack path; 
  
void InitLinkStack(LinkStack&L){
	L=new SNode;
	L->next=NULL;
}

void Push(LinkStack&L,ElementType e){
	SNode *p=new SNode;
	p->data=e;
	p->next=L->next;
	L->next=p;
}
void Pop(LinkStack&L){
	if(L->next==NULL){
		cout<<"The LinkStack is Empty"<<endl;
	}
	else{
	SNode*p;
	p=L->next;
	L->next=p->next;
	delete p;
 }
}
bool EmptyLinkStack(LinkStack L){
	if(L->next==NULL)
	return true;
	else
	return false;
}
 ElementType GetTop(LinkStack L){
	if(!EmptyLinkStack(L)){
		ElementType e;
		SNode*p =L;
		p=L->next;
		e=p->data;
		return e;
	}
	else{
		cout<<"鏈棧爲空"<<endl;
	}
}

 void Init(){
	cout<<"請分別輸入迷宮的行數和列數"<<endl;
    cin>>n; 
    cin>>m;
    cout<<"請輸入迷宮"<<endl;
    for(int i=1;i<=n;i++){
    	
		   	for(int j=1;j<=m;j++){
    	 	cin>>image[i][j]; 	 	
		 }
   	}
	
}
void PutIn(){
	int s1,s2, e1,e2;
	cout<<"請分別輸入起點的橫座標和縱座標"<<endl;
	cin>>s1;
	cin>>s2;
	if(s1<1||s1>n||s2<1||s2>m){ 
	cout<<"起點輸入範圍錯誤,請重新輸入"<<endl; 
	cin>>s1;
	cin>>s2; 
	
	}
	 
	cout<<"請分別輸入終點的橫座標和縱座標"<<endl;
	cin>>e1;
	cin>>e2;
	if(e1<1||e1>n||e2<1||e2>m){
       cout<<"終點輸入範圍錯誤,請重新輸入"<<endl;
       cin>>e1;
       cin>>e2;
	}
	sx=s1;
	sy=s2;
	ex=e1;
	ey=e2;
}
 
void Print(){
	  LinkStack p;
	  InitLinkStack(p);
	  //將path 內存儲的數據順序顛倒過來,以便於正向輸出     將path的最頂端元素放到 p 的最底端  
	  while(!EmptyLinkStack(path)){
	  	node tmp=GetTop(path);
	  	Push(p,tmp);
	  	Pop(path);
     }
      node tmp=GetTop(p);
	  tmp.x=sx;
	  tmp.y=sy;
	  Push(path,tmp);
	  cout<<"("<<tmp.x<<","<<tmp.y<<","<<tmp.direction<<")";
	  Pop(p);
	  while(!EmptyLinkStack(p)){
	  	tmp=GetTop(p);
	  	cout<<","<<"("<<tmp.x<<","<<tmp.y<<","<<tmp.direction<<")";
	   Pop(p);
	  }
	  cout<<endl;
	
}
void Deal(){
	node tmp;
	tmp.x=sx;
	tmp.y=sy;
	 Push(path,tmp);
	while(!EmptyLinkStack(path)){
		for(int i=1;i<=4;i++){
			tmp=GetTop(path);
			tmp.direction=i;
			Pop(path);
	        Push(path,tmp);
			tmp.x+=dir[i-1][0];
			tmp.y+=dir[i-1][1];
		if(tmp.x<1||tmp.x>n||tmp.y<1||tmp.y>m||image[tmp.x][tmp.y]||vis[tmp.x][tmp.y]) continue;
		if(tmp.x==ex&&tmp.y==ey){
			Print();
			return;
		}
	    vis[tmp.x][tmp.y]=1;
	    Push(path,tmp);
	    i=0; 	
		}
        Pop(path);
	}
	cout<<"No Way!"<<endl;
} 
 int main(){
 	InitLinkStack(path);   //初始化path 
	Init();                //創建迷宮 
	PutIn();               //輸入起點終點 
	Deal();                //探索路徑,並輸出結果的函數 
	return 0;	 
}



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