數據結構實驗二——迷宮的求解

實驗要求和內容

迷宮只有兩個門,一個叫做入口,另一個叫做出口。把一隻老鼠從一個無頂蓋的大盒子的入口處趕進迷宮。迷宮中設置很多隔壁,對前進方向形成了多處障礙,在迷宮的唯一出口處放置了一塊奶酪,吸引老鼠在迷宮中尋找通路以到達出口。求解迷宮問題,即找出從入口到出口的路徑。

實驗思路

考慮到迷宮的性質以及我們走迷宮的步驟,我們選擇回溯法來尋找通路,選擇棧這種數據結構用來保存一條完整的從入口到出口的路線。即從入口出發,按某一方向向前探索,若能走通(未走過的),即某處可以到達,則到達新點,否則試探下一方向 ; 若所有的方向均沒有通路,則沿原路返回前一點,換下一個方向再繼續試探,直到所有可能的通路都探索到,或找到一條通路,或無路可走又返回到入口點。

實驗內容和實驗步驟

需求分析

首先我們將迷宮設定爲 n×n(0<n<100)n×n \quad(0<n<100)個空格組成的矩陣 **(也可以設置成n×m,但個人感覺意義不大)**在程序中,我用一個map[100][100]的數組來儲存地圖,接着我選擇用數字來表示地圖:0 代表一個空格,1 代表障礙,2 代表起點,3 代表終點。

其次,爲了滿足用戶需求,我提供用戶自行輸入地圖,和程序隨機生成地圖兩種模式。其中隨機生成地圖障礙和空格的比例大致爲1:10,用戶也可以自行調整。

最後,我們將找到的那一條從起點到終點的通路連同地圖打印出來。

概要設計

爲了表示一個座標x,y,我們選擇使用結構體來存儲一個座標點。爲了保存一條完整的路徑,我們自定義一個棧來存儲。

定義如下:

// 結構體表示座標
typedef struct {
int x;
int y;
}PointPos;


// 棧數據類型的定義
typedef struct
{
PointPos data[StackSize];
int top;
}SeqStack;


// 定義棧的主要功能
// 初始化一個棧
void InitStack(SeqStack &S);
// 判斷棧是否爲空
int StackEmpty(SeqStack S);
// 判斷棧是否爲滿
int StackFull(SeqStack S);
// 壓棧
void push(SeqStack &S, PointPos x);
// 彈出
PointPos pop(SeqStack &S);
// 返回棧頂元素
PointPos top(SeqStack S);

主程序的流程:請用戶選擇自行輸入還是隨機生成地圖 -> 確定地圖 -> 尋找起點和終點 -> 利用Stack實現深度優先搜索 -> 將結果打印在屏幕上

詳細設計以及代碼分析

用戶選擇

爲了實現用戶選擇的功能,我編寫了兩個函數。

// 用戶自行輸入函數
void Choose(int n)
{
	printf("0 代表空格子,1 代表牆壁,2 代表起點,3 代表終點\n");
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> map[i][j];
}
// 隨機生成函數
void Random(int n)
{
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
		{
			// 設置空格和障礙的比例爲10:1
			if (rand() % 10 == 1)
				map[i][j] = 1;
			else
				map[i][j] = 0;
		}
	// 隨機設置起點和終點
	int a, b, c, d;
	a = rand() % n;
	b = rand() % n;
	c = rand() % n;
	d = rand() % n;
	map[a][b] = 2;
	map[c][d] = 3;
	printf("\n隨機生成的地圖是:\n");
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			printf("%d ", map[i][j]);
		}
		printf("\n");
	}
}

時間複雜度O(n2)O(n^2)

尋找起點和終點

遍歷一遍地圖,記錄下起點和終點

void FindPos(PointPos& start, PointPos& end)
{
	// 找到起點和終點座標
	int f = 0;
	SeqStack pointstack;
	// 初始化棧
	InitStack(pointstack);
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (2 == map[i][j])
			{
				// 找到起點
				start.x = i;
				start.y = j;
				f = f + 1;
			}
			else if (map[i][j] == 3)
			{
				// 找到終點
				end.x = i;
				end.y = j;
				f = f + 1;
			}
			if (2 == f) break;
		}
		if (2 == f) break;
	}
}

最壞時間複雜度O(n2)O(n^2)

利用棧實現深度優先遍歷算法

深度優先搜索(DFS)(棧類實現),可以被形象的描述爲“打破沙鍋問到底”,具體一點就是訪問一個頂點之後,我繼續訪問它的下一個鄰接的頂點,如此往復,直到當前頂點被訪問或者它不存在鄰接的頂點。

具體思路如下:先從起點開始,依次尋找上下左右的座標,如果是障礙就跳過,如果不是障礙就把這個點入棧,並且標記下來,如此往復。與此同時,我給每個遍歷過的點增加一定的權值,在相同條件下,優先走權值大的點。如果找不到任何通路,就做一個標記,打印”沒有任何通路“。

在一定條件下,可以保證有曼哈頓距離的最優解

具體代碼實現

void dfs(PointPos start, PointPos end)
{
	// 定義存儲棧
	SeqStack pointstack;
	InitStack(pointstack);
	// 標記路徑權值,定義點座標和臨時變量
	int flag = 4;
	PointPos pointpos, temp;
	// 將起點座標入棧,並進入循環搜索
	int cnt = 0;
	push(pointstack, start);
	while (!StackEmpty(pointstack))
	{
		cnt = 0;
		pointpos = top(pointstack);
		pop(pointstack);
		// count記錄每個座標點有路徑的方向個數
		int i, j, count = 0;
		i = pointpos.x;
		j = pointpos.y;
		//如果爲搜索過的路徑,置爲權值
		if (0 == map[i][j]) map[i][j] = flag;
		// 座標點上下是否可走
		if (i >= 0 && i <= n - 1)
		{
			if (i != 0 && 0 == map[i - 1][j])
			{
				temp.x = i - 1;
				temp.y = j;
				push(pointstack, temp);
				++count;
			}
			else if (3 == map[i - 1][j] && i != 0)
			{
				cnt++;
				break;
			}
			if (0 == map[i + 1][j] && i != n - 1)
			{
				temp.x = i + 1;
				temp.y = j;
				push(pointstack, temp);
				++count;
			}
			else if (3 == map[i + 1][j] && i != n - 1)
			{
				cnt++;
				break;
			}
		}
		// 座標點左右是否可走
		if (j >= 0 && j <= n - 1)
		{
			if (0 == map[i][j - 1] && j != 0)
			{
				temp.x = i;
				temp.y = j - 1;
				push(pointstack, temp);
				++count;
			}
			else if (3 == map[i][j - 1] && j != 0)
			{
				cnt++;
				break;
			}
			if (0 == map[i][j + 1] && j != n - 1)
			{
				temp.x = i;
				temp.y = j + 1;
				push(pointstack, temp);
				++count;
			}
			else if (3 == map[i][j + 1] && j != n - 1)
			{
				cnt++;
				break;
			}
		}
		// 關於有多條通路的處理
		if (1 != count)
			++flag;
	}
	// check=1表示沒有通路
	if (cnt == 0)
		check = 1;
}

打印結果

通過棧已經將可能的路徑標記下來了,現在只需要輸出就可以。並且如果check=1的話,證明沒有通路,可以作爲特判條件。

具體代碼實現

void Output(PointPos start)
{
	// 特判
	if (check == 1)
	{
		printf("沒有一條合適的路徑\n");
		return;
	}
	// 搜索到達的路徑
	int i, j;
	int ni = -1; int nj = -1;
	int max;
	i = start.x;
	j = start.y;
	max = map[i][j];
	// 如果不是終點
	while (3 != map[i][j])
	{
		// 上下判斷
		if (i >= 0 && i <= n - 1)
		{
			if (map[i - 1][j] > max && i != 0)
			{
				ni = i - 1;
				nj = j;
				max = map[i - 1][j];
			}
			if (map[i - 1][j] == 3 && i != 0)
				break;
			if (map[i + 1][j] > max&& i != n - 1)
			{
				ni = i + 1;
				nj = j;
				max = map[i + 1][j];
			}
			if (map[i + 1][j] == 3 && i != n - 1)
				break;
		}
		// 左右判斷
		if (j >= 0 && j <= n - 1)
		{
			if (map[i][j - 1] > max&& j != 0)
			{
				ni = i;
				nj = j - 1;
				max = map[i][j - 1];
			}
			if (map[i][j - 1] == 3 && j != 0)
				break;
			if (map[i][j + 1] > max&& j != n - 1)
			{
				ni = i;
				nj = j + 1;
				max = map[i][j + 1];
			}
			if (3 == map[i][j + 1] && j != n - 1)
				break;
		}
		// 標記爲路徑 即 -1
		if (ni != -1 && nj != -1)
			map[ni][nj] = -1;
		else
		{
			//	printf("沒有一條合適的路徑\n");
			return;
		}
		// 設置初始最大值
		max = 3;
		// 進入迭代
		i = ni;
		j = nj;
	}
	// 輸出結果
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			// 標記爲路徑
			if (-1 == map[i][j])
				cout << '*' << ' ';
			// 恢復原來的值
			else if (map[i][j] > 3)
				cout << 0 << ' ';
			else
				cout << map[i][j] << ' ';
		}
		cout << endl;
	}
}

調試分析

調試過程中遇到的問題

  1. 邊界條件處理的不好。當i== 0 或者 i==n-1時 需要特殊判斷
  2. 一開始無法解決沒有通路該如何顯示結果。後來想到,當棧爲空時還沒有訪問到終點,證明沒有任何一條通路到達終點。這樣就能解決這個問題了。
  3. 地圖和棧的初始化問題,本來地圖初始化是零,但是因爲我設置的空格也是0,所以這樣就會出現問題。最後我把地圖初始化成了-1.
  4. 結果不正確。主要是本來應該存在通路的情況卻顯示沒有。通過我的調試,我發現是因爲我在特判時出現了問題。break語句和cnt++語句搞反,導致還沒有自增1就退出了循環,debug了很久最終解決。

算法的時空分析

根據我的算法,打印地圖的時間複雜度爲O(n2)O(n^2)。深度優先搜素算法時間複雜度爲O(E)O(E),也就是所有邊的數量,(n1)×(2n1)+(n1)=2n(n1)(n-1)\times(2n-1)+(n-1)=2n(n-1),根據我的估算,時間複雜度大致爲O(n2)O(n^2)。深度優先搜索算法空間複雜度爲O(V)O(V)也就是所有頂點的個數,爲O(n2)O(n^2)。因此本算法,時空複雜度均爲O(n2)O(n^2)

實驗結果

初始界面

在這裏插入圖片描述
選擇用戶輸入

需要輸入n
在這裏插入圖片描述
輸入地圖

在這裏插入圖片描述

打印有解情況

在這裏插入圖片描述
若是輸入無解

在這裏插入圖片描述

選擇隨機生成地圖

保證曼哈頓距離最短
在這裏插入圖片描述

實驗總結

  1. 記得break;要在語句之後,否則還沒來得及執行就退出循環了。
  2. DFS算法也可以用棧實現,但是BFS 算法必須要用到隊列這種數據結構。
  3. 在設置地圖時要控制好障礙和空格的比例,不然一旦點多了就無法找到結果。
  4. 在無解時,可以通過檢查棧爲空時是否到達終點來判斷。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章