解救小哈——DFS算法舉例
一、問題引入
有一天,小哈一個人去玩迷宮。但是方向感不好的小哈很快就迷路了。小哼得知後便去解救無助的小哈。此時的小哼已經弄清楚了迷宮的地圖,現在小哼要以最快的速度去解救小哈。那麽,問題來了...
二、問題的分析
首先我們用一個二維數組來存儲這個迷宮,剛開始的時候,小哼處於迷宮的入口處(1,1),小哈在(p,q)。其實這道題的的本質就在於找從(1,1)到(p,q)的最短路徑。
此時擺在小哼面前的路有兩條,我們可以先讓小哼往右邊走,直到走不通的時候再回到這裏,再去嘗試另外一個方向。
在這裏我們規定一個順序,按照順時針的方向來嘗試(即右→下→左→上)。
我們先來看看小哼一步之內可以到達的點有哪些?只有(1,2)和(2,1)。
根據剛才的策略,我們先往右邊走,但右邊(1,3)有障礙物,所以只能往下(2,2)這個點走。但是小哈並不在(2,2)這個點上,所以小哼還得繼續往下走,直至無路可走或者找到小哈為止。
註意:並不是讓我們找到小哈此題就解決了。因為剛才只是嘗試了一條路的走法,而這條路並不一定是最短的。剛才很多地方在選擇方向的時候都有多種選擇,因此我們需要返回到這些地方繼續嘗試往別的方向走,直到把所有可能都嘗試一遍,最後輸出最短的一條路徑。
例如下圖就是一條可行的搜索路徑:
三、解決問題——深度優先搜索
(1)如何寫dfs函數。
dfs函數的功能是解決當前應該怎麽辦。而小哼處在某個點的時候需要處理的是:先檢查小哼是否已經到達小哈的位置,如果沒有到達則找出下一步可以走的地方。
為了解決這個問題,此處dfs()函數只需要維護三個參數,分別是當前這個點的x坐標,y坐標以及當前已經走過的步數step。
//dfs函數定義如下: void dfs(int x,int y,int step) { return 0; }
(2)判斷是否已經到達小哈的位置。
只需要判斷當前的坐標是否與小哈的坐標相等就可以了,如果相等就標明已經到達小哈的位置。
void dfs(int x,int y,int step) { if(x==p && y==1) //判斷是否到達小哈的位置 { if(step<min) min=step; //更新最小值 return; //這步很重要! } return 0; }
(3)如何獲得下一個方向的坐標(此處定義一個方向數組)。
int next[4][2]={ {0,1},//向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0},//向上走 };
通過這個方向數組,使用循環就可以方便地得到下一步的坐標。
這裏將下一步的橫坐標用tx存儲,縱坐標用ty存儲。
for(k=0;k<=3;k++) { /*計算下一個點的坐標*/ tx=x+next[k][0]; ty=y+next[k][1]; }
(4)對下一個點(tx,ty)進行判斷(是否越界,是否有障礙物,是否已經在路徑中)。
在這裏我們用book[tx][ty]來記錄格子[tx][ty]是否已經在路徑中。
如果這個點符合所有的要求,就對這個點進行下一步的擴展,即dfs(tx,ty,step+1)。
註意這裏是step+1,因為一旦從這個點開始繼續往下嘗試,就意味著步數已經增加了1。
for(k=0;k<=3;k++) { /*計算下一個點的坐標*/ tx=x+next[k][0]; ty=y+next[k][1]; if(tx<1 || tx>n || ty<1 || ty>m) //判斷是否越界 continue; /*判斷該點是否為障礙物或者已經在路徑中*/ if(a[tx][ty]==0 && book[tx][ty]==0) { book[tx][ty]=1; //標記這個點已經走過 dfs(tx,ty,step+1); //開始嘗試下一個點 book[tx][ty]=0; //嘗試結束,取消這個點的標記 } }
四、完整代碼
#include<stdio.h> int n,m,p,q,min=99999999; int a[51][51],book[51][51]; void dfs(int x,int y,int step) { int next[4][2]={ {0,1},//向右走 {1,0},//向下走 {0,-1},//向左走 {-1,0},//向上走 }; int tx,ty,k; if(x==p && y==1) //判斷是否到達小哈的位置 { if(step<min) min=step; //更新最小值 return; } /*枚舉四種走法*/ for(k=0;k<=3;k++) { /*計算下一個點的坐標*/ tx=x+next[k][0]; ty=y+next[k][1]; if(tx<1 || tx>n || ty<1 || ty>m) //判斷是否越界 continue; /*判斷該點是否為障礙物或者已經在路徑中*/ if(a[tx][ty]==0 && book[tx][ty]==0) { book[tx][ty]=1; //標記這個點已經走過 dfs(tx,ty,step+1); //開始嘗試下一個點 book[tx][ty]=0; //嘗試結束,取消這個點的標記 } } return; } int main() { int i,j,startx,starty; scanf("%d %d",&n,&m); //讀入n和m,n為行,m為列 /*讀入迷宮*/ for(i=1;i<=n;i++) for(j=1;j<=m;j++) scanf("%d",&a[i][j]); scanf("%d %d %d %d",&startx,&starty,&p,&q); //讀入起點和終點坐標 /*從起點開始搜索*/ book[startx][starty]=1; //標記起點已經在路徑中,防止後面重復走 dfs(startx,starty,0); //第一個參數是起點的x坐標,以此類推是起點的y坐標,初始步數為0 printf("%d",min); //輸出最短步數 return 0; }
五、寫在最後
發明深度優先算法的是John E.Hopcroft 和 Robert E.Tarjan。他們並不是研究全排列或者迷宮問題時發明了這個算法。
1971~1972年,他們在斯坦福大學研究圖的連通性(任意兩點是否可以相互到達)和平面性(圖中所有的邊相互不交叉。在電路板上設計布線的時候,要求線與線不能交叉,這就是平面性的一個實際應用),發明了這個算法。他們也因此獲得了1986年的圖靈獎。
在授獎儀式上,當年全國象棋程序比賽的優勝者說他的程序用的就是深度優先搜索算法,這是以其制勝的關鍵。
通過本次學習,我明白了當我們遇到這種需要“分身”,需要不斷嘗試完成的事情時,可以嘗試使用深度優先搜索算法,因為計算機的運算速度還是很強的,我們要借助他的優勢,完成一些生活中比較繁瑣重復的事情。
(註:文章內容源自 啊哈磊的《啊哈算法》——很有意思的一本算法入門書!)
解救小哈——DFS算法舉例