深度優先算法
定義
wiki上的解釋:
深度優先搜索算法(英語:Depth-First-Search,簡稱DFS)是一種用於遍歷或搜索樹或圖的算法。沿着樹的深度遍歷樹的節點,儘可能深的搜索樹的分支。當節點v的所在邊都己被探尋過,搜索將回溯到發現節點v的那條邊的起始節點。這一過程一直進行到已發現從源節點可達的所有節點爲止。如果還存在未被發現的節點,則選擇其中一個作爲源節點並重復以上過程,整個進程反覆進行直到所有節點都被訪問爲止。屬於盲目搜索。
當然看到上面這句話的時候,我並沒有理解什麼到底是DFS,因此又看了很多人的說話,有了下面一段話:
DFS的思想是從一個頂點V0開始,沿着一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然後從另一條路開始走到底。
DFS適合此類題目:給定初始狀態跟目標狀態,要求判斷從初始狀態到目標狀態是否有解。
深度與廣度的比較
我們搜索一個圖是按照樹的層次來搜索的。
我們假設一個節點衍生出來的相鄰節點平均的個數是N個,那麼當起點開始搜索的時候,隊列有一個節點,當起點拿出來後,把它相鄰的節點放進去,那麼隊列就有N個節點,當下一層的搜索中再加入元素到隊列的時候,節點數達到了N2,你可以想想,一旦N是一個比較大的數的時候,這個樹的層次又比較深,那這個隊列就得需要很大的內存空間了。
於是廣度優先搜索的缺點出來了:在樹的層次較深&子節點數較多的情況下,消耗內存十分嚴重。廣度優先搜索適用於節點的子節點數量不多,並且樹的層次不會太深的情況。
那麼深度優先就可以克服這個缺點,因爲每次搜的過程,每一層只需維護一個節點。但回過頭想想,廣度優先能夠找到最短路徑,那深度優先能否找到呢?深度優先的方法是一條路走到黑,那顯然無法知道這條路是不是最短的,所以你還得繼續走別的路去判斷是否是最短路?
於是深度優先搜索的缺點也出來了:難以尋找最優解,僅僅只能尋找有解。其優點就是內存消耗小,克服了剛剛說的廣度優先搜索的缺點。
數字爲搜索順序
代碼(轉)
public class DFSTest {
// 存儲節點信息
private char[] vertices;
// 存儲邊信息(鄰接矩陣)
private int[][] arcs;
// 圖的節點數
private int vexnum;
// 記錄節點是否已被遍歷
private boolean[] visited;
// 初始化
public DFSTest(int n) {
vexnum = n;
vertices = new char[n];
arcs = new int[n][n];
visited = new boolean[n];
for (int i = 0; i < vexnum; i++) {
for (int j = 0; j < vexnum; j++) {
arcs[i][j] = 0;
}
}
}
// 添加邊(無向圖)
public void addEdge(int i, int j) {
// 邊的頭尾不能爲同一節點
if (i == j)return;
arcs[i][j] = 1;
arcs[j][i] = 1;
}
// 設置節點集
public void setVertices(char[] vertices) {
this.vertices = vertices;
}
// 設置節點訪問標記
public void setVisited(boolean[] visited) {
this.visited = visited;
}
// 打印遍歷節點
public void visit(int i){
System.out.print(vertices[i] + " ");
}
// 從第i個節點開始深度優先遍歷
private void traverse(int i){
// 標記第i個節點已遍歷
visited[i] = true;
// 打印當前遍歷的節點
visit(i);
// 遍歷鄰接矩陣中第i個節點的直接聯通關係
for(int j=0;j<vexnum;j++){
// 目標節點與當前節點直接聯通,並且該節點還沒有被訪問,遞歸
if(arcs[i][j]==1 && visited[j]==false){
traverse(j);
}
}
}
// 圖的深度優先遍歷(遞歸)
public void DFSTraverse(){
// 初始化節點遍歷標記
for (int i = 0; i < vexnum; i++) {
visited[i] = false;
}
// 從沒有被遍歷的節點開始深度遍歷
for(int i=0;i<vexnum;i++){
if(visited[i]==false){
// 若是連通圖,只會執行一次
traverse(i);
}
}
}
// 圖的深度優先遍歷(非遞歸)
public void DFSTraverse2(){
// 初始化節點遍歷標記
for (int i = 0; i < vexnum; i++) {
visited[i] = false;
}
Stack<Integer> s = new Stack<Integer>();
for(int i=0;i<vexnum;i++){
if(!visited[i]){
//連通子圖起始節點
s.add(i);
do{
// 出棧
int curr = s.pop();
// 如果該節點還沒有被遍歷,則遍歷該節點並將子節點入棧
if(visited[curr]==false){
// 遍歷並打印
visit(curr);
visited[curr] = true;
// 沒遍歷的子節點入棧
for(int j=vexnum-1; j>=0 ; j-- ){
if(arcs[curr][j]==1 && visited[j]==false){
s.add(j);
}
}
}
}while(!s.isEmpty());
}
}
}
public static void main(String[] args) {
DFSTest g = new DFSTest(9);
char[] vertices = {'A','B','C','D','E','F','G','H','I'};
g.setVertices(vertices);
g.addEdge(0, 1);
g.addEdge(0, 5);
g.addEdge(1, 0);
g.addEdge(1, 2);
g.addEdge(1, 6);
g.addEdge(1, 8);
g.addEdge(2, 1);
g.addEdge(2, 3);
g.addEdge(2, 8);
g.addEdge(3, 2);
g.addEdge(3, 4);
g.addEdge(3, 6);
g.addEdge(3, 7);
g.addEdge(3, 8);
g.addEdge(4, 3);
g.addEdge(4, 5);
g.addEdge(4, 7);
g.addEdge(5, 0);
g.addEdge(5, 4);
g.addEdge(5, 6);
g.addEdge(6, 1);
g.addEdge(6, 3);
g.addEdge(6, 5);
g.addEdge(6, 7);
g.addEdge(7, 3);
g.addEdge(7, 4);
g.addEdge(7, 6);
g.addEdge(8, 1);
g.addEdge(8, 2);
g.addEdge(8, 3);
System.out.print("深度優先遍歷(遞歸):");
g.DFSTraverse();
System.out.println();
System.out.print("深度優先遍歷(非遞歸):");
g.DFSTraverse2();
}
}
----------
Output:
深度優先遍歷(遞歸):A B C D E F G H I
深度優先遍歷(非遞歸):A B C D E F G H I
博客中有些內容引自:
深度優先搜索(DFS)理解DFS概念上很清晰