資料結構(十三)
阿新 • • 發佈:2019-01-23
迷宮問題
此處有前提
這篇博文主要是借迷宮問題來掌握按鄰接表儲存的圖的遍歷搜尋問題。
迷宮問題就是給定一個迷宮、指定出入口,找尋可達路徑。
先附上執行結果(DFS):
下面我嘗試分步驟來幫助解讀程式碼:
1.隨機生成地圖
//生成隨機地圖 #include<iostream> #include <stdlib.h> #include <time.h> #include<fstream> #define N 20 int data[N+2][N+2]; //全域性變數預設為0 using namespace std; int main(){ fstream outFile,readFile; outFile.open("2_data.txt",ios::out); srand((unsigned)time(NULL)); for(int i=0;i<N+2;i++){ for(int j=0;j<N+2;j++){ data[i][j]=1; } } for(int i=0+1;i<N+1;i++){ for(int j=0+1;j<N+1;j++){ data[i][j]=rand()%2; } } for(int i=0;i<N+2;i++){ for(int j=0;j<N+2;j++){ outFile<<data[i][j]<<" "; } outFile<<endl; } outFile.close(); cout<<"成功生成隨機迷宮地圖!(1代表不可行,0代表可行)\n"; readFile.open("2_data.txt",ios::in); if(readFile.fail()){ cout<<"讀取檔案失敗!\n"; exit(-2); } int i=0,j=0; while(!readFile.eof()){ char temp; readFile>>temp; data[i][j++]=temp-48; if(j==N+2){ i++; j=0; } } for(int i=0;i<N+2;i++){ for(int j=0;j<N+2;j++){ cout<<data[i][j]<<" "; } cout<<endl; } readFile.close(); return 0; }
2.讀取資料構建圖
void getData(int data[N+2][N+2]){ //二維資料的大小寫死 fstream readFile; readFile.open("2_data.txt",ios::in); if(readFile.fail()){ cout<<"讀取檔案失敗!\n"; exit(-2); } int i=0,j=0; while(!readFile.eof()){ char temp; readFile>>temp; data[i][j++]=temp-48; if(j==N+2){ i++; j=0; } } readFile.close(); }
void CreateGraph(Graph &G,int data[][N+2]){ for(int i=1;i<N+1;i++){ for(int j=1;j<N+1;j++){ int cnt=1,x,y; while(cnt<=4){ //右下左上四種情況 要麼順時針要麼逆時針 switch(cnt){ case 1: x=i+1; y=j; break; case 2: x=i; y=j+1; break; case 3: x=i-1; y=j; break; case 4: x=i; y=j-1; break; default: break; } cnt++; if(data[x][y]==0){ //兩點之間有邊相連 ArcNode *q=(ArcNode*)malloc(sizeof(ArcNode)); Node temp; temp.x=x; temp.y=y; q->adjVex=temp; q->nextArc=G.AdjList[i][j].firstArc; G.AdjList[i][j].firstArc=q; } } } } }
3.對圖DFS/BFS
bool DFS(Graph G,Node start,Node end,int data[N+2][N+2]){
Stack S;
InitStack(S);
Push(S,end); //反向加入 正向輸出
vis[end.x][end.y]=1;
while(!isStackEmpty(S)){
Node tempN;
tempN=Top(S);
ArcNode *tempV=G.AdjList[tempN.x][tempN.y].firstArc;
while(tempV){ //尋找它的所有可走的鄰接點
if(vis[tempV->adjVex.x][tempV->adjVex.y]==0){
Push(S,tempV->adjVex);
vis[tempV->adjVex.x][tempV->adjVex.y]=1;
if(tempV->adjVex.x==start.x&&tempV->adjVex.y==start.y){ //找到路徑並輸出
while(!isStackEmpty(S)){
Pop(S,tempN);
map[tempN.x][tempN.y]='*';
}
cout<<"---------------\n";
cout<<"DFS找尋迷宮路徑\n";
cout<<"---------------\n";
cout<<"‘+’代表牆\n‘'’代表能走卻沒走的區域\n‘*’代表可行路徑\n";
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
if(i==start.x&&j==start.y){
cout<<"S ";
}else if(i==end.x&&j==end.y){
cout<<"E ";
}else{
cout<<map[i][j]<<" ";
}
}
cout<<endl;
}
DestroyStack(S);
return true;
}
break; //一旦找到就跳出內層迴圈(體現遞迴思想的地方)
}
tempV=tempV->nextArc;
}
if(tempV==NULL){ //當前點沒有可走的鄰接點就出棧
Pop(S,tempN);
}
}
return false;
}
bool BFS(Graph G,Node start,Node end,int data[N+2][N+2]){
QNode *Queue=(QNode*)malloc((N+2)*(N+2)*sizeof(QNode));
int front=0,rear=1;
Queue[front].data=start;
Queue[front].pre=-1;
vis[start.x][start.y]=1;
QNode tempQ;
Node tempN;
while(front<rear){ //佇列不為空
tempQ=Queue[front];
tempN=tempQ.data;
ArcNode *tempV=G.AdjList[tempN.x][tempN.y].firstArc;
while(tempV){ //尋找它的所有可走的鄰接點
if(vis[tempV->adjVex.x][tempV->adjVex.y]==0){
Queue[rear].data.x=tempV->adjVex.x;
Queue[rear].data.y=tempV->adjVex.y;
Queue[rear].pre=front;
rear++;
vis[tempV->adjVex.x][tempV->adjVex.y]=1;
if(tempV->adjVex.x==end.x&&tempV->adjVex.y==end.y){ //找到路徑
while(front!=-1){
map[Queue[front].data.x][Queue[front].data.y]='*';
front=Queue[front].pre;
}
cout<<"---------------\n";
cout<<"BFS找尋迷宮路徑\n";
cout<<"---------------\n";
cout<<"‘+’代表牆\n‘'’代表能走卻沒走的區域\n‘*’代表可行路徑\n";
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
if(i==start.x&&j==start.y){
cout<<"S ";
}else if(i==end.x&&j==end.y){
cout<<"E ";
}else{
cout<<map[i][j]<<" ";
}
}
cout<<endl;
}
free(Queue);
return true;
}
}
tempV=tempV->nextArc;
}
front++; //最前面的數出佇列是為了下一步讓它的鄰接點進隊
}
return false;
}
以下附上完整原始碼:
#define SIZE sizeof(Node)
#define STACK_INITSIZE 10*SIZE
#define STACK_INCREAMENT 5*SIZE
#include<iostream>
#include<stdlib.h>
#include<fstream>
#define N 20
char map[N+2][N+2];
int vis[N+2][N+2];
using namespace std;
typedef struct{
int x;
int y;
}Node;
typedef struct ArcNode{
Node adjVex; //該邊的終點位置
struct ArcNode *nextArc; //指向下一條邊的指標
}ArcNode;
typedef struct{
//VertexType data; //此題背景下結點不需要資訊(結點值沒有意義)
ArcNode *firstArc; //指向與該結點相鄰的第一條邊
}VNode;
typedef struct{
VNode **AdjList; //鄰接表頭結點陣列指標(此題的申請的空間是二維的便於操作)
// int vexNum; //本題不用
// int arcNum;
}Graph; //鄰接表方式儲存
void getData(int data[N+2][N+2]){ //二維資料的大小寫死
fstream readFile;
readFile.open("2_data.txt",ios::in);
if(readFile.fail()){
cout<<"讀取檔案失敗!\n";
exit(-2);
}
int i=0,j=0;
while(!readFile.eof()){
char temp;
readFile>>temp;
data[i][j++]=temp-48;
if(j==N+2){
i++;
j=0;
}
}
readFile.close();
}
void InitGraph(Graph &G){
G.AdjList=(VNode **)malloc((N+2)*(sizeof(VNode*)));
for(int i=0;i<N+2;i++){
G.AdjList[i]=(VNode *)malloc((N+2)*(sizeof(VNode)));
}
for(int i=0;i<N+2;i++){
for(int j=0;j<N+2;j++){
G.AdjList[i][j].firstArc=NULL;
}
}
}
void DestroyGraph(Graph &G){
for(int i=0;i<N+2;i++){
for(int j=0;j<N+2;j++){
while(G.AdjList[i][j].firstArc){
ArcNode *temp=G.AdjList[i][j].firstArc;
G.AdjList[i][j].firstArc=G.AdjList[i][j].firstArc->nextArc;
free(temp);
}
}
free(G.AdjList[i]);
}
}
void CreateGraph(Graph &G,int data[][N+2]){
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
int cnt=1,x,y;
while(cnt<=4){ //右下左上四種情況 要麼順時針要麼逆時針
switch(cnt){
case 1:
x=i+1;
y=j;
break;
case 2:
x=i;
y=j+1;
break;
case 3:
x=i-1;
y=j;
break;
case 4:
x=i;
y=j-1;
break;
default:
break;
}
cnt++;
if(data[x][y]==0){ //兩點之間有邊相連
ArcNode *q=(ArcNode*)malloc(sizeof(ArcNode));
Node temp;
temp.x=x;
temp.y=y;
q->adjVex=temp;
q->nextArc=G.AdjList[i][j].firstArc;
G.AdjList[i][j].firstArc=q;
}
}
}
}
}
typedef struct{ //自定義的順序棧
Node *top;
Node *base;
int stackSize;
}Stack;
void InitStack(Stack &S){
S.base=(Node *)malloc(STACK_INITSIZE);
if(!S.base){
cout<<"申請空間失敗!\n";
exit(-2);
}
S.top=S.base;
S.stackSize=STACK_INITSIZE;
}
void DestroyStack(Stack &S){
free(S.base);
S.base=NULL;
S.stackSize=0;
}
bool isStackEmpty(Stack S){
if(S.base==S.top)
return true;
return false;
}
void Push(Stack &S,Node node){
if(S.top-S.base>=S.stackSize/SIZE){ //即將溢位
S.base=(Node*)realloc(S.base,S.stackSize+STACK_INCREAMENT);
if(!S.base){
cout<<"申請空間失敗!\n";
exit(-2);
}
S.stackSize+=STACK_INCREAMENT;
}
*(S.top)=node;
S.top++;
}
void Pop(Stack &S,Node &node){
if(S.top==S.base){
cout<<"當前棧為空\n";
exit(-2);
}
S.top--;
node=*(S.top);
}
Node Top(Stack S){
if(S.top==S.base){
cout<<"當前棧為空\n";
exit(-2);
}
return *(S.top-1);
}
bool DFS(Graph G,Node start,Node end,int data[N+2][N+2]){
Stack S;
InitStack(S);
Push(S,end); //反向加入 正向輸出
vis[end.x][end.y]=1;
while(!isStackEmpty(S)){
Node tempN;
tempN=Top(S);
ArcNode *tempV=G.AdjList[tempN.x][tempN.y].firstArc;
while(tempV){ //尋找它的所有可走的鄰接點
if(vis[tempV->adjVex.x][tempV->adjVex.y]==0){
Push(S,tempV->adjVex);
vis[tempV->adjVex.x][tempV->adjVex.y]=1;
if(tempV->adjVex.x==start.x&&tempV->adjVex.y==start.y){ //找到路徑並輸出
while(!isStackEmpty(S)){
Pop(S,tempN);
map[tempN.x][tempN.y]='*';
}
cout<<"---------------\n";
cout<<"DFS找尋迷宮路徑\n";
cout<<"---------------\n";
cout<<"‘+’代表牆\n‘'’代表能走卻沒走的區域\n‘*’代表可行路徑\n";
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
if(i==start.x&&j==start.y){
cout<<"S ";
}else if(i==end.x&&j==end.y){
cout<<"E ";
}else{
cout<<map[i][j]<<" ";
}
}
cout<<endl;
}
DestroyStack(S);
return true;
}
break; //一旦找到就跳出內層迴圈(體現遞迴思想的地方)
}
tempV=tempV->nextArc;
}
if(tempV==NULL){ //當前點沒有可走的鄰接點就出棧
Pop(S,tempN);
}
}
return false;
}
typedef struct{
Node data;
int pre; //記錄上一個點在佇列中的下標
}QNode;
bool BFS(Graph G,Node start,Node end,int data[N+2][N+2]){
QNode *Queue=(QNode*)malloc((N+2)*(N+2)*sizeof(QNode));
int front=0,rear=1;
Queue[front].data=start;
Queue[front].pre=-1;
vis[start.x][start.y]=1;
QNode tempQ;
Node tempN;
while(front<rear){ //佇列不為空
tempQ=Queue[front];
tempN=tempQ.data;
ArcNode *tempV=G.AdjList[tempN.x][tempN.y].firstArc;
while(tempV){ //尋找它的所有可走的鄰接點
if(vis[tempV->adjVex.x][tempV->adjVex.y]==0){
Queue[rear].data.x=tempV->adjVex.x;
Queue[rear].data.y=tempV->adjVex.y;
Queue[rear].pre=front;
rear++;
vis[tempV->adjVex.x][tempV->adjVex.y]=1;
if(tempV->adjVex.x==end.x&&tempV->adjVex.y==end.y){ //找到路徑
while(front!=-1){
map[Queue[front].data.x][Queue[front].data.y]='*';
front=Queue[front].pre;
}
cout<<"---------------\n";
cout<<"BFS找尋迷宮路徑\n";
cout<<"---------------\n";
cout<<"‘+’代表牆\n‘'’代表能走卻沒走的區域\n‘*’代表可行路徑\n";
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
if(i==start.x&&j==start.y){
cout<<"S ";
}else if(i==end.x&&j==end.y){
cout<<"E ";
}else{
cout<<map[i][j]<<" ";
}
}
cout<<endl;
}
free(Queue);
return true;
}
}
tempV=tempV->nextArc;
}
front++; //最前面的數出佇列是為了下一步讓它的鄰接點進隊
}
return false;
}
void showMap(int data[N+2][N+2]){
cout<<"原地圖如下:\n‘+’代表牆\n‘'’代表可行域\n";
for(int i=0;i<N+2;i++){
for(int j=0;j<N+2;j++){
if(data[i][j]==0){
map[i][j]='\'';
}else{
map[i][j]='+';
}
}
}
for(int i=1;i<N+1;i++){
for(int j=1;j<N+1;j++){
cout<<map[i][j]<<" ";
}
cout<<endl;
}
}
int main(){
int data[N+2][N+2];
int choice=0;
getData(data);
Graph G;
InitGraph(G);
CreateGraph(G,data);
showMap(data);
Node start,end;
cout<<"請指定入口橫縱座標(1-"<<N<<"):";
cin>>start.x>>start.y;
//start.x=start.y=1;
cout<<"請指定出口橫縱座標(1-"<<N<<"):";
cin>>end.x>>end.y;
//end.x=end.y=20;
while(choice!=1&&choice!=2){
cout<<"請指定您想用的方法:1.DFS 2.BFS\n";
cin>>choice;
if(choice!=1&&choice!=2){
cout<<"請您在1,2之間選取!"<<endl;
system("pause");
system("cls");
}
}
system("cls");
if(choice==1){
if(DFS(G,start,end,data)==false){
cout<<"沒有從該入口到該出口的路徑!"<<endl;
}
}else if(choice==2){
if(BFS(G,start,end,data)==false){
cout<<"沒有從該入口到該出口的路徑!"<<endl;
}
}
DestroyGraph(G);
return 0;
}
由於這是一個課設,所以有不少程式碼是為了最終的人機互動相對良好,大可跳過不看,核心在於資料對接、兩演算法以及資料結構。