一、需求分析
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 引用類型的形參L將L棧頂的元素(最新的元素)彈出棧。
5、判空操作模塊
程序解釋:函數接收一個LinkStack 類型的形參L,返回值是bool型,如果判斷該棧爲空,返回true,如果不爲空,返回false
6、獲取棧頂元素的模塊
程序解釋:函數接收一個LinkStack類型的形參L,返回值爲ElementType 類型,如果L不爲空,則返回L的棧頂元素。
7、創建迷宮模塊
程序解釋:利用兩層for循環完成對二維數組的賦值,其中n是要輸入的行數,m是要輸入的列數,i和j從1開始,說明二維數組的第一個元素是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;
}