在之前的文章裏我們介紹了深度優先遍歷的思想,並且以數組全排列爲例子實現了算法編碼,本篇我們通過深度優先搜索解決迷宮最短路徑問題。用程序實現查詢從迷宮入口到終點的最短路徑,需要越過障礙物,並且不能超出迷宮界限。
算法圖解
假如有如下迷宮,我們需要找到從入口到終點的最短路徑,其中標記鎖的位置爲障礙物不可抵達。
我們先來看第一步可以達到的點有哪些?只有(1,2)和(2,1)。假如我們先往右走來到(2,1)這個點,下一步能到達的點只有(2,2)了,因爲(1,3)是障礙物,(1,1)已經走過了,也不能走。但是我們依然沒有到達終點,所以得繼續往下走,直至無路可走或到達終點爲止。注意!並不是到達終點就結束了,因爲到達終點只是嘗試了一條路,不一定就是最短的。剛纔在某些點我們有多個方向可供選擇,因此我們需要返回到這些點繼續嘗試往別的地方走,直到把所有可能都嘗試一遍,最後輸出最短路徑。
現在我們嘗試用深度優先搜索來實現這個方法dfs()。首先,dfs方法需要處理的內容包括:先檢查當前是否已到達終點處,如果沒有到達終點則尋找下一步可以走的地方。爲了達到目的,dfs函數需要三個參數,分別對應x,y座標以及當前步數step。定義如下:
static void dfs(int x,int y,int step)
{
}
判斷當前是否到達終點很簡單,只需要判斷和終點座標是否相同
static int p ; //終點y
static int q ; //終點座標y
static int min; //當前最短路徑步數
static void dfs(int x,int y,int step)
{
//判斷是否到達終點
if (x==p&&y==q)
{
//更新最小值
if (step<min)
{
min = step;
}
return; //到達終點則返回
}
}
假如沒有到達終點,則需要找出下一步可以走的位置。因爲有四個方向可以走,我們定義一個方向數組next,如下:
static int[][] next = new int[][]
{
new int[] { 0, 1 }, //向右
new int[] { 1, 0 }, //向下
new int[] { 0, -1 },//向左
new int[] { -1, 0 } //向上
};
通過這個方向數組,我們可以獲得下一步的座標,這裏我們用tx存儲下一步橫座標,ty存儲下一步縱座標。
for (int i = 0; i < next.Length; i++)
{
//計算下一步的座標
tx = x + next[i][0];
ty = y + next[i][1];
}
接下來對下一步的點(tx,ty)進行判斷,包括是否越界,是否爲障礙物,以及這個點是否已經走過,我們用book數組來記錄哪些點已經走過。
如果這個點滿足所有要求,就對這個點進行下一步的擴展,即dfs(step+1),一旦進行下一步嘗試就意味着步數已經增加了1。
for (int i = 0; i < next.Length; i++)
{
//計算下一步的座標
tx = x + next[i][0];
ty = y + next[i][1];
//判斷是否越界
if (tx < 1 || tx > n || ty < 1 || ty > m)
{
continue;
}
//判斷該點是否爲障礙物或者已經在路徑中
if (a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = 1; //標記這個點已經走過
way.Add(string.Format("({0},{1})", tx, ty)); //記錄到路徑中,用於輸出
var index = way.Count - 1;
dfs(tx, ty, step + 1); //開始嘗試下一步
book[tx][ty] = 0; //嘗試結束後取消這個點的標記
way.RemoveAt(index);
}
}
我們來看一下完整的實現及調用:
class Program
{
static void Main(string[] args)
{
int i, j, startx, starty;
//初始化迷宮信息5*4
n = 5;
m = 4;
for (i = 1; i <= n; i++)
{
a[i] = new int[5];
book[i] = new int[5];
for (j = 1; j <= m; j++)
{
//(1,3),(3,3),(4,2),(5,4)處爲障礙物
if ((i == 1 && j == 3) || (i == 3 && j == 3) || (i == 4 && j == 2) || (i == 5 && j == 4))
{
a[i][j] = 1;
}
else
{
a[i][j] = 0;
}
}
}
//終點爲(4,3)
p = 4;
q = 3;
way.Add("(1,1)");
book[1][1] = 1;
dfs(1, 1, 0); //起點爲(1,1),開始遍歷
Console.WriteLine("最短步數爲:" + min);
Console.ReadKey();
}
static int n, m, p, q, min = 99999;
static int[][] a = new int[6][]; //記錄障礙物
static int[][] book = new int[6][];//記錄走過的路徑
static List<string> way = new List<string>();
static void dfs(int x, int y, int step)
{
int[][] next = new int[][]
{
new int[] { 0, 1 }, //向右
new int[] { 1, 0 }, //向下
new int[] { 0, -1 },//向左
new int[] { -1, 0 } //向上
};
int tx, ty;
//判斷是否到達終點
if (x == p && y == q)
{
//更新最小值
if (step < min)
{
min = step;
}
foreach (var item in way)
{
Console.Write(item + " ");
}
Console.WriteLine("到達終點,步數{0}", step);
return; //到達終點則返回
}
for (int i = 0; i < next.Length; i++)
{
//計算下一步的座標
tx = x + next[i][0];
ty = y + next[i][1];
//判斷是否越界
if (tx < 1 || tx > n || ty < 1 || ty > m)
{
continue;
}
//判斷該點是否爲障礙物或者已經在路徑中
if (a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty] = 1; //標記這個點已經走過
way.Add(string.Format("({0},{1})", tx, ty)); //記錄到路徑中,用於輸出
var index = way.Count - 1;
dfs(tx, ty, step + 1); //開始嘗試下一步
book[tx][ty] = 0; //嘗試結束後取消這個點的標記
way.RemoveAt(index);
}
}
return;
}
}
我們在Main方法中按照上面的迷宮圖初始化了迷宮,然後從起點(1,1)調用深度優先搜索查找並打印所以通向終點的路徑,最後輸出最短步數,結果如下圖:
至此已經完成了迷宮最短路徑的搜索,如果對深度優先遍歷思想不太熟悉的可以參考我的上一篇文章:C#深度優先遍歷