深度優先搜索(DFS)與廣度優先搜索(BFS)的Java實現
1、基礎部分
在圖中實現最基本的操作之一就是搜索從一個指定頂點可以到達哪些頂點,比如從武漢出發的高鐵可以到達哪些城市,一些城市可以直達,一些城市不能直達。現在有一份全國高鐵模擬圖,要從某個城市(頂點)開始,沿著鐵軌(邊)移動到其他城市(頂點),有兩種方法可以用來搜索圖:深度優先搜索(DFS)和廣度優先搜索(BFS)。它們最終都會到達所有連通的頂點,深度優先搜索通過棧來實現,而廣度優先搜索通過隊列來實現,不同的實現機制導致不同的搜索方式。
1.1 深度優先搜索
深度優先搜索算法有如下規則:
規則1:如果可能,訪問一個鄰接的未訪問頂點,標記它,並將它放入棧中。
規則2:
當不能執行規則 1 時,如果棧不為空,就從棧中彈出一個頂點。規則3:如果不能執行規則 1 和規則 2 時,就完成了整個搜索過程。
對於上圖,應用深度優先搜索如下:假設選取 A 頂點為起始點,並且按照字母優先順序進行訪問,那麽應用規則 1 ,接下來訪問頂點 B,然後標記它,並將它放入棧中;再次應用規則 1,接下來訪問頂點 F,再次應用規則 1,訪問頂點 H。我們這時候發現,沒有 H 頂點的鄰接點了,這時候應用規則 2,從棧中彈出 H,這時候回到了頂點 F,但是我們發現 F 也除了 H 也沒有與之鄰接且未訪問的頂點了,那麽再彈出 F,這時候回到頂點 B,同理規則 1 應用不了,應用規則 2,彈出 B,這時候棧中只有頂點 A了,然後 A 還有未訪問的鄰接點,所有接下來訪問頂點 C,但是 C又是這條線的終點,所以從棧中彈出它,再次回到 A,接著訪問 D,G,I,最後也回到了 A,然後訪問 E,但是最後又回到了頂點 A,這時候我們發現 A沒有未訪問的鄰接點了,所以也把它彈出棧。現在棧中已無頂點,於是應用規則 3,完成了整個搜索過程。
深度優先搜索在於能夠找到與某一頂點鄰接且沒有訪問過的頂點。這裏以鄰接矩陣為例,找到頂點所在的行,從第一列開始向後尋找值為1的列;列號是鄰接頂點的號碼,檢查這個頂點是否未訪問過,如果是這樣,那麽這就是要訪問的下一個頂點,如果該行沒有頂點既等於1(鄰接)且又是未訪問的,那麽與指定點相鄰接的頂點就全部訪問過了(後面會用算法實現)。
1.2 廣度優先搜索
深度優先搜索要盡可能的遠離起始點,而廣度優先搜索則要盡可能的靠近起始點,它首先訪問起始頂點的所有鄰接點,然後再訪問較遠的區域,這種搜索不能用棧實現,而是用隊列實現。
規則1:訪問下一個未訪問的鄰接點(如果存在),這個頂點必須是當前頂點的鄰接點,標記它,並把它插入到隊列中。
規則2:如果已經沒有未訪問的鄰接點而不能執行規則 1 時,那麽從隊列列頭取出一個頂點(如果存在),並使其成為當前頂點。
規則3:如果因為隊列為空而不能執行規則 2,則搜索結束。
對於上面的圖,應用廣度優先搜索:以A為起始點,首先訪問所有與 A 相鄰的頂點,並在訪問的同時將其插入隊列中,現在已經訪問了 A,B,C,D和E。這時隊列(從頭到尾)包含 BCDE,已經沒有未訪問的且與頂點 A 鄰接的頂點了,所以從隊列中取出B,尋找與B鄰接的頂點,這時找到F,所以把F插入到隊列中。已經沒有未訪問且與B鄰接的頂點了,所以從隊列列頭取出C,它沒有未訪問的鄰接點。因此取出 D 並訪問 G,D也沒有未訪問的鄰接點了,所以取出E,現在隊列中有 FG,在取出 F,訪問 H,然後取出 G,訪問 I,現在隊列中有 HI,當取出他們時,發現沒有其它為訪問的頂點了,這時隊列為空,搜索結束。
2、代碼實現
實現深度優先搜索的棧 StackX.class:
package testOffer.graphpro; //實現深度優先搜索的棧 public class StackX { private final int SIZE = 20; private int[] st; private int top; public StackX(){ st = new int[SIZE]; top = -1; } public void push(int j){ st[++top] = j; } public int pop(){ return st[top--]; } public int peek(){ return st[top]; } public boolean isEmpty(){ return (top==-1); } }
實現廣度優先搜索的隊列Queue.class:
package testOffer.graphpro; //實現廣度優先搜索的隊列 public class QueueX { private final int SIZE = 20; private int[] queArray; private int front; private int rear; public QueueX(){ queArray = new int[SIZE]; front = 0; rear = -1; } public void insert(int j){ if (rear == SIZE-1) rear = -1; queArray[++rear] = j; } public int remove(){ int temp = queArray[front++]; if (front == SIZE){ front = 0; } return temp; } public boolean isEmpty(){ return (rear+1 == front || front+SIZE-1 == rear); } }
圖代碼 Graph.class:
package testOffer.graphpro; public class Graph { private final int MAX_VERTS = 20;//表示頂點的個數 private Vertex vertexList[];//用來存儲頂點的數組 private int adjMat[][];//用鄰接矩陣來存儲邊,數組元素表示沒有邊界,1表示有邊界 private int nVerts;//頂點個數 private StackX theStack;//用棧實現深度優先搜索 private QueueX queue;//用隊列實現廣度優先搜索 /** * 頂點類 * */ class Vertex{ public char label; public boolean wasVisited; public Vertex(char label){ this.label = label; wasVisited = false; } } public Graph(){ vertexList = new Vertex[MAX_VERTS]; adjMat = new int[MAX_VERTS][MAX_VERTS]; nVerts = 0;//初始化頂點個數為0 //初始化鄰接矩陣所有元素都為0,即所有頂點都沒有邊 for (int i=0;i<MAX_VERTS;i++){ for (int j=0;j<MAX_VERTS;j++){ adjMat[i][j] = 0; } } theStack = new StackX(); queue = new QueueX(); } //將頂點添加到數組中,是否訪問標誌置為wasVisited=false(未訪問)、 public void addVertex(char lab){ vertexList[nVerts++] = new Vertex(lab); } //註意用鄰接矩陣表示邊,是對稱的,兩部分都要賦值 public void addEdge(int start,int end){ adjMat[start][end] = 1; adjMat[end][start] = 1; } //打印某個頂點表示的值 public void displayVertex(int v){ System.out.print(vertexList[v].label+" "); } /**深度優先搜索算法 * 1、用peek方法檢查棧頂的頂點 * 2、用getAdjUnvisitedVertex方法找到當前棧頂鄰接且未被訪問的頂點 * 3、第二步返回值不等於-1則找到下一個未訪問的鄰接頂點,訪問這個頂點,並入棧 * 如第二步返回值等於-1,則沒有找到,出棧 * */ public void depthFirstSearch(){ //從第一個頂點開始訪問 vertexList[0].wasVisited = true;//訪問之後標記為true displayVertex(0); theStack.push(0); while (!theStack.isEmpty()){ //找到棧當前頂點鄰接且未被訪問的頂點 int v = getAdjUnvisitedVertex(theStack.peek()); if (v==-1){//如果當前頂點值為-1,則表示沒有鄰接且未被訪問的頂點,那麽出棧頂點 theStack.pop(); }else { //否則訪問下一個鄰接點 vertexList[v].wasVisited = true; displayVertex(v); theStack.push(v); } } //棧訪問完畢,重置所有標記位為false for (int i=0;i<nVerts;i++){ vertexList[i].wasVisited = false; } } //找到與某一頂點鄰接且未被訪問的頂點 public int getAdjUnvisitedVertex(int v){ for (int i=0;i<nVerts;i++){ //v頂點與i頂點相鄰且未被訪問 if (adjMat[v][i]==1&&vertexList[i].wasVisited==false) return i; } return -1; } /**廣度優先搜索 * 1、用remove方法檢查棧頂的棧頂 * 2、試圖找到這個頂點還未被訪問的鄰接點 * 3、如果沒有找到,該頂點出列 * 4、如果找到這樣的頂點,訪問這個頂點,並把它放入隊列中 * */ public void breadthFirstSearch(){ vertexList[0].wasVisited = true; displayVertex(0); queue.insert(0); int v2; while(!queue.isEmpty()){ int v1 = queue.remove(); while ((v2 =getAdjUnvisitedVertex(v1))!=-1){ vertexList[v2].wasVisited = true; displayVertex(v2); queue.insert(v2); } } //搜索完畢,初始化,便於下次搜索 for (int i=0;i<nVerts;i++){ vertexList[i].wasVisited = false; } } public static void main(String[] args) { Graph graph = new Graph(); graph.addVertex(‘A‘); graph.addVertex(‘B‘); graph.addVertex(‘C‘); graph.addVertex(‘D‘); graph.addVertex(‘E‘); graph.addEdge(0,1);//AB graph.addEdge(1,2);//BC graph.addEdge(0,3);//AD graph.addEdge(3,4);//DE System.out.println("深度優先搜索算法:"); graph.depthFirstSearch(); System.out.println(); System.out.println("---------------"); System.out.println("廣度優先搜索算法:"); graph.breadthFirstSearch(); } }
深度優先搜索(DFS)與廣度優先搜索(BFS)的Java實現