C語言-老鼠走迷宮(廣度尋路演算法)
老鼠走迷宮-c語言(基於廣度優先的尋路演算法)
深讀優先尋路演算法原文:https://blog.csdn.net/qq_42476927/article/details/81868068
本文是基於之前的深度優先尋路演算法改進而來,參考了許多廣度遍歷的程式碼結合自己程式碼改進成為現在的樣子,基本上優化了之前的所有缺點,相較於之前尋路的速度快了N倍,當然深度優先本來就不是用於遍歷出最短路徑,廣度優先的尋路演算法才是真正適合用於尋找最短路徑,剛開始思路沒那麼清晰所以才有了深度遍歷最短路徑。
-
以下是全部的函式和部分變數:
-
void game() //走迷宮遊戲,包含時間統計,以及對老鼠的移動控制,遊戲模組
-
void create_plat() //新建地圖,並存入檔案的模組 地圖檔案是plat.txt
-
void show_plat() //地圖展示模組
-
void alter_plat() //修改地圖模組
-
void read_plat() //讀取檔案裡的地圖模組
-
//廣度優先尋路演算法
int NextPos(int *i,int *j,int count) //i行 j列,count為遍歷上下左右的條件
void En_stack(int i,int j,int k) //將通路的i,j計入棧中
void De_stack(int *i,int *j,int *k) //讀取棧中的元素
void Path_BFS() //廣度演算法遍歷,尋找最短路徑核心
void path_best() //標記最短路徑模組並顯示 -
void path_find(int p,int q) //尋找通關路徑的模組,深度演算法
-
新增元素:
typedef struct
{
int x;
int y;
}Axis;
//用於存放路徑結構體
typedef struct
{
int parent;
int child;
Axis childpos;
}Stack;Stack S[M*N]; //用於儲存的棧
int top=0,bottom=0; //標記棧的兩個定位元素
經過n次除錯,改程式碼,才調試出來,去掉了原先深度尋找最短路徑的部分程式碼,新增的visit陣列是用來廣度遍歷標記的,然後配合S棧找出返回的路徑,返回路徑是由parent和child標記定位棧中元素返回的,並不是一個一個出棧,然後標記出路徑再輸出,當然這個程式還是有一點小瑕疵,就是一旦使用20x20空地圖,再用尋找全部路徑這個功能,就會出現bug,因為尋找全部路徑使用的是深度遍歷,空地圖如果大了,就需要更多的運算,花費很多時間,但是9x9地圖是沒有問題的,有興趣的話可以自己改著試一下。
以下是原始碼:
大家可以改一下試試,程式設計軟體vc6.0
#include <stdio.h>
#include <stdlib.h>
#include<conio.h>
#include<time.h>
#define M 9
#define N 9
int maze[M][N] = {2,2,2,2,2,2,2,2,2,
2,0,0,0,2,0,0,0,2,
2,0,2,0,0,0,2,2,2,
2,0,0,2,2,0,0,0,2,
2,0,2,0,1,2,2,0,2,
2,0,2,0,2,0,0,0,2,
2,0,2,0,2,2,0,2,2,
2,0,0,0,0,2,0,3,2,
2,2,2,2,2,2,2,2,2}; //初始預設地圖
int visit[M][N]={ 2,2,2,2,2,2,2,2,2,
2,0,0,0,2,0,0,0,2,
2,0,2,0,0,0,2,2,2,
2,0,0,2,2,0,0,0,2,
2,0,2,0,1,2,2,0,2,
2,0,2,0,2,0,0,0,2,
2,0,2,0,2,2,0,2,2,
2,0,0,0,0,2,0,3,2,
2,2,2,2,2,2,2,2,2}; //用於廣度遍歷的陣列
typedef struct
{
int x;
int y;
}Axis;
//用於存放路徑結構體
typedef struct
{
int parent;
int child;
Axis childpos;
}Stack;
Stack S[M*N]; //用於儲存的棧
//全域性定義的變數
int m=4,n=4; //老鼠的初始位置
int p=4,q=4; //老鼠位置二次定位
int x=7,y=7; //糧倉初始位置
int g; //g:統計有效路徑,防止路徑過多,造成迴圈過度;
int top=0,bottom=0; //標記棧的兩個定位元素
void game() //走迷宮遊戲,包含時間統計,以及對老鼠的移動控制,遊戲模組
{
int i,j,v,t=0; //t為初始時間
char str;
time_t start,end;
start=time(NULL);
maze[x][y]=3;
m=p;
n=q;
maze[m][n]=1; //重新定位老鼠位置
printf("顯示迷宮:\n"); //地圖初始化
for (i = 0 ; i < M ; i++)
{
for (j = 0 ; j < N ; j++)
{
if(maze[i][j] == 2){
printf("▉");
}
else if(maze[i][j]==1)
{
printf("鼠");
}
else if(maze[i][j]==3)
{
printf("糧");
}
else
{
printf(" ");
}
}
printf("\n");
}
printf("(按w↑s↓a← d→移動)\n請在15秒內通關☆(-o⌒)\n"); //初始化地圖
while(1)
{
printf("時間:%d\r",t);
if(kbhit()) //輸入控制
{
str=getch();
if(str=='w') //上
{
if(maze[m-1][n]==0)
{
maze[m-1][n]=1;
maze[m][n]=0;
m=m-1;
}
else if(maze[m-1][n]==3)
{
maze[m][n]=0;
v=1;
}
}
else if(str=='s') //下
{
if(maze[m+1][n]==0)
{
maze[m+1][n]=1;
maze[m][n]=0;
m=m+1;
}
else if(maze[m+1][n]==3)
{
maze[m][n]=0;
v=1;
}
}
else if(str=='a') //左
{
if(maze[m][n-1]==0)
{
maze[m][n-1]=1;
maze[m][n]=0;
n=n-1;
}
else if(maze[m][n-1]==3)
{
maze[m][n]=0;
v=1;
}
}
else if(str=='d') //右
{
if(maze[m][n+1]==0)
{
maze[m][n+1]=1;
maze[m][n]=0;
n=n+1;
}
else if(maze[m][n+1]==3)
{
maze[m][n]=0;
v=1;
}
}
else;
system("cls");
printf("顯示迷宮:\n"); //顯示遊戲地圖
for (i = 0 ; i < M ; i++)
{
for (j = 0 ; j < N ; j++)
{
if(maze[i][j] == 2)
{
printf("▉");
}
else if(maze[i][j]==1)
{
printf("鼠");
}
else if(maze[i][j]==3)
{
printf("糧");
}
else
{
printf(" ");
}
}
printf("\n");
}
printf("(按w↑s↓a← d→移動)\n請在15秒內通關☆(-o⌒) \n");
}
else;
if(v==1) //判斷是否通關
{
printf("\n恭喜通關!( ̄▽ ̄)~*\n");
system("pause");
break;
}
if(t>15) //規定時間到後結束遊戲
{
printf("\n未在規定時間內通關,遊戲失敗。(╯︵╰)\n");
maze[m][n]=0; //清除最後所在位置
system("pause");
break;
}
end=time(NULL);
t=difftime(end,start);
}
}
void create_plat() //新建地圖,並存入檔案的模組
{
int i,j,s;
cc: printf("請輸入0和2,0代表通路,2代表牆體(數字用空格隔開),輸入規格是%dx%d,有邊緣牆體無需再次輸入( ̄▽ ̄)/\n",M-2,M-2);
for(i=1;i<=M-2;i++)
{
printf("第%d行:",i);
for(j=1;j<=N-2;j++)
{
scanf("%d",&s);
if(s==0||s==2)
maze[i][j]=s;
else
{
system("cls");
printf("輸入錯誤請重新輸入╮(╯﹏╰)╭\n");
goto cc;
}
}
}
aa: printf("請設定老鼠的初始位置x,y即行列(1~%d,1~%d):\n",M-2,N-2);
p=q=NULL;
m=n=NULL;
//清除來自鍵盤的30多個快取字元,防止死迴圈bug
for(i=0;i<30;i++)
fflush(stdin);
scanf("%d,%d",&p,&q);
if(p<=(M-2)&&q<=(M-2)&&p>0&&q>0)
maze[p][q]=1;
else
{
system("cls");
printf("輸入錯誤,請重新輸入,在%dx%d的範圍內。(;′⌒`)\n",M-2,N-2);
goto aa;
}
bb: printf("請設定糧倉的位置x,y:\n");
//清除來自鍵盤的30多個快取字元,防止死迴圈bug
for(i=0;i<30;i++)
fflush(stdin);
scanf("%d,%d",&x,&y);
if(x<=(M-2)&&y<=(N-2)&&x>0&&y>0&&(x!=p||y!=q))
maze[x][y]=3;
else
{
system("cls");
printf("輸入錯誤,請重新輸入,在%dx%d的範圍內。(;′⌒`)\n",M-2,N-2);
goto bb;
}
//檔案儲存地圖
FILE *fp;
fp=fopen("plat.txt","w");
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
fprintf(fp,"%d\t",maze[i][j]);
fprintf(fp,"%c",'\n');
}
fprintf(fp,"%d\t",p);
fprintf(fp,"%d\t",q);
fprintf(fp,"%d\t",x);
fprintf(fp,"%d\t",y);
fclose(fp);
printf("地圖新建完成,並儲存成功!!!ヾ(^Д^*)/\n");
system("pause");
}
void show_plat() //地圖展示模組
{
int i,j;
system("cls");
maze[x][y]=3;
m=p;
n=q;
maze[m][n]=1;
printf("顯示迷宮:\n"); //顯示遊戲地圖
for (i = 0 ; i < M ; i++)
{
for (j = 0 ; j < N ; j++)
{
if(maze[i][j] == 2)
{
printf("▉");
}
else if(maze[i][j]==1)
{
printf("鼠");
}
else if(maze[i][j]==3)
{
printf("糧");
}
else
{
printf(" ");
}
}
printf("\n");
}
printf("已顯示地圖☆(-o⌒)\n");
system("pause");
}
void alter_plat() //修改地圖模組
{
int i,j,select,a,b;
FILE *fp;
while(1)
{
system("cls");
m=p;
n=q;
maze[m][n]=1;
printf("顯示迷宮:\n"); //顯示遊戲地圖
for (i = 0 ; i < M ; i++)
{
for (j = 0 ; j < N ; j++)
{
if(maze[i][j] == 2)
{
printf("▉");
}
else if(maze[i][j]==1)
{
printf("鼠");
}
else if(maze[i][j]==3)
{
printf("糧");
}
else
{
printf(" ");
}
}
printf("\n");
}
printf(" =============----修改地圖------===================\n");
printf(" |請選擇: |\n");
printf(" | 1.修改為牆體; |\n");
printf(" | 2.修改為通路; |\n");
printf(" | 3.儲存修改地圖; |\n");
printf(" | 0.退出修改功能; |\n");
printf(" ===========(請輸入相應數字執行其功能)===========\n");
fflush(stdin); //清除鍵入的快取
scanf("%d",&select);
if(select<4 && select>=0)
{
switch(select)
{
case 1: printf("圍牆內為修改範圍,範圍是%dx%d\n",M-2,N-2);
printf("請輸入座標x,y(行,列)修改地圖:\n");
//清除來自鍵盤的30多個快取字元,防止死迴圈bug
for(i=0;i<30;i++)
fflush(stdin);
scanf("%d,%d",&a,&b);
if(a<=(M-2)&&b<=(N-2)&&a>0&&b>0&&maze[a][b]!=1&&maze[a][b]!=3)
maze[a][b]=2;
else
{
printf("輸入錯誤請重新輸入,不能在圍牆、糧倉和老鼠的位置修改哦(*/ω\*)\n");
system("pause");
}
break;
case 2: printf("圍牆內為修改範圍,範圍是%dx%d\n",M-2,N-2);
printf("請輸入座標x,y(行,列)修改地圖:\n");
//清除來自鍵盤的30多個快取字元,防止死迴圈bug
for(i=0;i<30;i++)
fflush(stdin);
scanf("%d,%d",&a,&b);
if(a<=(M-2)&&b<=(N-2)&&a>0&&b>0&&maze[a][b]!=1&&maze[a][b]!=3)
maze[a][b]=0;
else
{
printf("輸入錯誤請重新輸入,不能在圍牆、糧倉和老鼠的位置修改哦(*/ω\*)\n");
system("pause");
}
break;
case 3:
{ //檔案形式儲存修改後地圖
fp=fopen("plat.txt","w");
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
fprintf(fp,"%d\t",maze[i][j]);
fprintf(fp,"%c",'\n');
}
fprintf(fp,"%d\t",p);
fprintf(fp,"%d\t",q);
fprintf(fp,"%d\t",x);
fprintf(fp,"%d\t",y);
fclose(fp);
printf("地圖修改完成,並儲存成功!!!(* ̄︶ ̄)\n");
system("pause");
}
break;
case 0: break;
}
//同步陣列maze到visit
for(i=0;i<M;i++)
for(j=0;j<N;j++)
visit[i][j]=maze[i][j];
}
else
{
printf("請按規定輸入哦 ̄ω ̄=\n");
system("pause");
}
if(select==0)
break;
}
}
void read_plat() //讀取檔案裡的地圖模組
{
int i,j;
FILE *fp;
fp=fopen("plat.txt","r");
if(!fp)
{
printf("檔案不存在,請重新開啟(╥╯^╰╥)\n");
system("pause");
return;
}
else
{
for(i=0;i<M;i++) //讀取到maze陣列
{
for(j=0;j<N;j++)
fscanf(fp,"%d\t",&maze[i][j]);
fscanf(fp,"\n");
}
fscanf(fp,"%d\t",&p);
fscanf(fp,"%d\t",&q); //讀取老鼠的位置
fscanf(fp,"%d\t",&x);
fscanf(fp,"%d\t",&y); //讀取糧倉的位置
printf("讀取成功,請檢視新地圖!( ̄▽ ̄)~*\n");
//將maze拷貝到visit
for(i=0;i<M;i++)
for(j=0;j<N;j++)
visit[i][j]=maze[i][j];
system("pause");
}
fclose(fp);
}
//廣度優先尋路演算法
int NextPos(int *i,int *j,int count) //i行 j列,count為遍歷上下左右的條件
{
switch(count)
{
case 1: (*j)++; //右
return 1;
case 2: (*i)++; //下
return 1;
case 3: (*j)--; //左
return 1;
case 4: (*i)--; //上
return 1;
default:
return 0;
}
}
void En_stack(int i,int j,int k) //將通路的i,j計入棧中
{
S[top].childpos.x=i;
S[top].childpos.y=j;
S[top].parent=k;
S[top].child=top;
top++;
}
void De_stack(int *i,int *j,int *k) //讀取棧中的元素
{
*i=S[bottom].childpos.x;
*j=S[bottom].childpos.y;
*k=S[bottom].child;
bottom++;
}
void Path_BFS() //廣度演算法遍歷,尋找最短路徑核心
{
int i,j,k,count,store_i,store_j;;
En_stack(p,q,-1);
visit[p][q]=1;
while(1)
{
count=1;
De_stack(&i,&j,&k);
store_i=i;store_j=j; //儲存當前位置
while(NextPos(&i,&j,count))
{
count++;
if(visit[i][j]==3||!visit[i][j])
{
En_stack(i,j,k);
visit[i][j]=1;
if(i==x&&j ==y)
return;
}
i=store_i;j=store_j;
}
}
}
void path_best() //標記最短路徑模組並顯示
{
int i,j,k;
//最短路徑標記
k=top-1;
while(k!=-1)
{
i=S[k].childpos.x;
j=S[k].childpos.y;
maze[i][j]=5;
k=S[k].parent;
}
//輸出最短路徑
printf("最短路徑:\n");
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
if(maze[i][j] == 2)
printf("█");
else if(maze[i][j]==0)
printf(" ");
else
printf("の");
printf("\n");
}
//地圖恢復
k=top-1;
while(k!=-1)
{
i=S[k].childpos.x;
j=S[k].childpos.y;
maze[i][j]=0;
k=S[k].parent;
}
top=0;
bottom=0;
for(i=0;i<M;i++)
for(j=0;j<N;j++)
visit[i][j]=maze[i][j];
maze[p][q]=1;
maze[x][y]=3;
}
//廣度搜索over
void path_find(int p,int q) //尋找通關路徑的模組,深度演算法
{
int i,j,c;
maze[p][q]=1;
c=g;
if(c==15) //防止過度迴圈,暫時只能顯示15條路徑,可調節。
{
printf("已列舉10種可行方案!!方案太多可能列舉不完,所以最多列舉15個( ̄︶ ̄)↗\r");
maze[p][q]=0;
return;
// exit(0);
}
if (maze[p][q+1]==3||maze[p][q-1]==3||maze[p+1][q]==3||maze[p-1][q]==3) //判斷當前位置上下左右是否有糧倉
{
printf("顯示路徑: \n");
for (i = 0 ; i < M ; i++)
{
for (j = 0 ; j< N ; j++)
{
if(maze[i][j] == 2)
{
printf("█");
}
else if(maze[i][j] == 1)
printf("の");
else if(maze[i][j]==3)
printf("糧");
else
printf(" ");
}
printf("\n");
}
printf("已顯示方案,如上!請稍等。。。\n");
g++;
}
if(g!=15)
{
if(maze[p][q+1] == 0) path_find(p, q+1); //右
if(maze[p+1][q] == 0) path_find(p+1, q); //下
if(maze[p][q-1] == 0) path_find(p, q-1); //左
if(maze[p-1][q] == 0) path_find(p-1, q); //上
}
maze[p][q]=0;
}
void main() //主函式,選單控制介面
{
/*全域性變數:m,n老鼠位置,p,q老鼠初始位置,x,y糧倉的初始位置,top,bottom用於指向棧中元素,標記位置;
g統計所有有效路徑的次數,最多十五次,防止因為路徑過多而死迴圈;結構體:Axis;Stack;
區域性變數:l,a,b,str,i,j,k,v,s,t,select 時間變數time_t start,end 指標fp 巨集定義 M N */
int select,k;
while(1)
{
system("cls");
printf(" =============老鼠走迷宮遊戲 n.0===================\n");
printf(" |請選擇: |\n");
printf(" | 1.開始遊戲; |\n");
printf(" | 2.新建地圖; |\n");
printf(" | 3.檢視地圖; |\n");
printf(" | 4.修改地圖; |\n");
printf(" | 5.讀取地圖; |\n");
printf(" | 6.顯示最短路徑; |\n");
printf(" | 7.顯示通關所有路徑; |\n");
printf(" | 0.退出系統; |\n");
printf(" ===========(請輸入相應數字執行其功能)===========\n");
printf(" 注意事項:\n 採用廣度優先搜尋演算法,尋找最短路徑,經過測試應該沒有bug了(;≧Д≦)y\n");
for(k=0;k<30;k++)
fflush(stdin); //清除鍵盤輸入的scanf快取,防止死迴圈
scanf("%d",&select);
if(select>=0 && select<8) //鍵盤輸入檢錯
{
switch(select)
{
case 0: exit(0);
case 1: system("cls"); //清除選單
game(); //開始遊戲
break;
case 2: create_plat(); //新建地圖
break;
case 3: show_plat(); //顯示地圖
break;
case 4: alter_plat(); //修改地圖
break;
case 5: read_plat(); //在檔案裡讀取地圖
break;
case 6: Path_BFS(); //廣度優先尋路演算法核心
path_best(); //顯示及恢復地圖
system("pause");
break;
case 7: g=0;
path_find(p,q); //尋找所有通關路徑
printf("已顯示所有路徑!(~ ̄▽ ̄)~ \n");
system("pause");
break;
default:
break;
}
}
else
{
printf("請按規定輸入哦 ̄ω ̄=\n");
system("pause");
}
}
}
/*2018.8.22 14:33 作者:zero丶0 廣度尋路演算法的最後一個版本,n次除錯,應該沒什麼問題了*/