1. 程式人生 > >C語言-老鼠走迷宮(廣度尋路演算法)

C語言-老鼠走迷宮(廣度尋路演算法)

老鼠走迷宮-c語言(基於廣度優先的尋路演算法)

深讀優先尋路演算法原文:https://blog.csdn.net/qq_42476927/article/details/81868068 

本文是基於之前的深度優先尋路演算法改進而來,參考了許多廣度遍歷的程式碼結合自己程式碼改進成為現在的樣子,基本上優化了之前的所有缺點,相較於之前尋路的速度快了N倍,當然深度優先本來就不是用於遍歷出最短路徑,廣度優先的尋路演算法才是真正適合用於尋找最短路徑,剛開始思路沒那麼清晰所以才有了深度遍歷最短路徑。

  • 以下是全部的函式和部分變數:

  1. void game()  //走迷宮遊戲,包含時間統計,以及對老鼠的移動控制,遊戲模組

  2. void create_plat()   //新建地圖,並存入檔案的模組   地圖檔案是plat.txt

  3. void show_plat()   //地圖展示模組

  4. void alter_plat()    //修改地圖模組

  5. void read_plat()  //讀取檔案裡的地圖模組

  6. //廣度優先尋路演算法 

    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()                          //標記最短路徑模組並顯示

  7. void path_find(int p,int q)  //尋找通關路徑的模組,深度演算法

  8. 新增元素:

    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次除錯,應該沒什麼問題了*/