優先順序搜尋演算法
1. 優先順序與優先順序數
廣度優先搜尋(BFS)在每一步迭代中會優先考查更早被發現的頂點,深度優先搜尋(DFS)會優先考查最後被發現的頂點。兩種選擇策略都等效於給所有頂點賦予不同的優先順序,而且隨著演算法的推進不斷調整;而每一步迭代所選取的頂點,都是當前優先順序最高者。
按照這種理解,包括BFS和DFS在內的幾乎所有的圖搜尋演算法,都可以納入統一的框架,即優先順序搜尋(priority-first search,PFS)。
在 ofollow,noindex">《圖基礎知識整理和程式碼實現》 一文中,我們為頂點類Vertex設定了一個int型的成員變數priority(優先順序數),並提供了get和set方法用於頂點優先順序數的讀取和修改。 (這裡我們約定優先順序數越大的頂點優先順序越低,初始狀態下priority設定為int的最大值)
2. 優先順序搜尋的基本框架
優先順序搜尋演算法的框架可具體實現為如下程式碼。
/** * 功能描述: 優先順序搜尋框架 * @param: updater 優先順序更新器,負責對頂點的優先順序進行更新,不同的更新策略可以實現不同的演算法 * @param: visitedOperation 對優先訪問的節點的訪問後,執行的操作,由使用者自己實現 * @return: * @auther: 楊贇 * @date: 2018/10/3 20:52 */ public void pfs(Tv data, PriorityUpdater<Tv> updater, VisitedOperation<Tv> visitedOperation) { Integer i = hashMap.get(data);//根據頂點key找到頂點在向量中的秩 if(i == null) return;//不存在該頂點,立即返回 reset(); int v = i;//初始化 do {//逐一檢查所有頂點 if(status(v) == VStatus.UNDISCOVERED) //一旦遇到未發現頂點 PFS(v, updater, visitedOperation);//即從該頂點出發啟動一次PFS }while (i!=(v=(++v%n)));//按序號檢查,故不漏補重 } private void PFS(int i, PriorityUpdater<Tv> updater, VisitedOperation<Tv> visitedOperation) { Vertex<Tv> vertex = V.get(i); vertex.setPriority(0); //0的優先順序最大 vertex.setStatus(VStatus.VISITED);//當前頂點已訪問 vertex.setParent(-1);//當前頂點沒有夫頂點 visitedOperation.doSomething(vertex);//訪問完頂點後幹什麼 while (true){ for (int j=firstNeighbor(i);j>-1;j=nextNeighbor(i,j)){//遍歷所有鄰居 updater.updatePriority(this,i,j);//對所有鄰居的優先順序進行更新 } for (int shortest = Integer.MAX_VALUE,j=0;j<n;j++){//遍歷所有頂點 if(status(j) == VStatus.UNDISCOVERED){//如果頂點未被發現 if(shortest > priority(j)){ shortest = priority(j); i = j;//在所有未被發現的頂點中找出優先順序數最小,即優先順序最大的頂點 } } } if(status(i) == VStatus.VISITED) break;//連通域內的所有頂點都已被訪問,結束 V.get(i).setStatus(VStatus.VISITED);//頂點設定為已訪問狀態 E.get(V.get(i).getParent()).get(i).setType(EType.TREE);//該頂點的父頂點與該頂點之間為TREE邊 visitedOperation.doSomething(V.get(i));//訪問完頂點後幹什麼 } }
優先順序更新器PriorityUpdater<Tv>介面(藉助PriorityUpdater<Tv>介面,使演算法設計者得以根據不同的問題需求,實現相應地更新策略)。
package com.whut.DataStructure.graph.interfaces; import com.whut.DataStructure.graph.GraphMatrix; /** * @Auther: 楊贇 * @Date: 2018/10/2 20:56 * @Description: */ public interface PriorityUpdater<Tv> { void updatePriority(GraphMatrix<Tv> graph, int vertex, int neighbor); }
訪問操作器VisitedOperation<Tv>介面(藉助VisitedOperation<Tv>介面,使演算法設計者可以按頂點的訪問次序,對訪問到的頂點進行一定的操作,比如列印該頂點)。
package com.whut.DataStructure.graph.interfaces; import com.whut.DataStructure.graph.entity.Vertex; /** * @Auther: 楊贇 * @Date: 2018/10/3 8:58 * @Description: 訪問頂點後的自定義功能介面 */ public interface VisitedOperation<Tv> { void doSomething(Vertex<Tv> vertex);//訪問頂點後,乾點什麼 }
可見,PFS搜尋的基本過程和功能與常規的圖搜尋演算法一樣,也是以迭代方式逐步引入頂點和邊,最終構成一顆遍歷樹(或遍歷森林)。而每次迭代中總是引入優先順序數最小(優先順序最大)的頂點,並按照不同的策略更新其鄰接頂點的優先順序數。下面藉助該框架,實現BFS和DFS。
3. 基於PFS實現BFS和DFS
/** * 功能描述: 基於PFS的BFS實現 * @param: visitedOperation 訪問 * @return: * @auther: 楊贇 * @date: 2018/10/4 11:41 */ public void bfs2(Tv data, VisitedOperation<Tv> visitedOperation){ pfs(data, new PriorityUpdater<Tv>() { @Override public void updatePriority(GraphMatrix<Tv> graph, int vertex, int neighbor) { if(graph.status(neighbor) == VStatus.UNDISCOVERED){ if(graph.priority(neighbor) > graph.priority(vertex)+1){ graph.setPriority(neighbor,graph.priority(vertex)+1); graph.setParent(neighbor,vertex);//越晚發現,優先順序數越大,優先順序越低 } } } }, visitedOperation); } /** * 功能描述: 基於PFS的DFS實現 * @param: * @return: * @auther: 楊贇 * @date: 2018/10/4 11:57 */ public void dfs2(Tv data, VisitedOperation<Tv> visitedOperation){ pfs(data, new PriorityUpdater<Tv>() { @Override public void updatePriority(GraphMatrix<Tv> graph, int vertex, int neighbor) { if(graph.status(neighbor) == VStatus.UNDISCOVERED){ if(graph.priority(neighbor) > graph.priority(vertex)-1){ graph.setPriority(neighbor,graph.priority(vertex)-1); graph.setParent(neighbor,vertex);//越晚發現,優先順序數越小,優先順序越高 } } } }, visitedOperation); }
PFS搜尋由兩層迴圈構成,其中內層迴圈又含並列的兩個迴圈,在基於PFS的BFS和DFS演算法中,updatePriority函式的執行需要常數的時間。所以演算法的總體複雜度為O(n 2 )。
4. 測試
採用 《圖基礎知識整理和程式碼實現》 )一文中的例子對新的BFS和DFS演算法進行測試
private static void bfsTest(String start) { GraphMatrix<String> graphMatrix = new GraphMatrix<String>(); graphMatrix.insert("A"); graphMatrix.insert("B"); graphMatrix.insert("C"); graphMatrix.insert("D"); graphMatrix.insert("E"); graphMatrix.insert("F"); graphMatrix.insert("G"); graphMatrix.insert("S"); graphMatrix.insert("S","A",new Edge(1,1)); graphMatrix.insert("S","C",new Edge(1,2)); graphMatrix.insert("S","D",new Edge(1,3)); graphMatrix.insert("A","C",new Edge(1,4)); graphMatrix.insert("A","E",new Edge(1,5)); graphMatrix.insert("C","B",new Edge(1,6)); graphMatrix.insert("D","B",new Edge(1,7)); graphMatrix.insert("E","F",new Edge(1,8)); graphMatrix.insert("E","G",new Edge(1,9)); graphMatrix.insert("G","F",new Edge(1,10)); graphMatrix.insert("G","B",new Edge(1,11)); VisitedOperation<String> operation = new VisitedOperation<String>() { @Override public void doSomething(Vertex<String> vertex) { System.out.print(" "+vertex.getData()); } }; System.out.print("bfs2結果:"); graphMatrix.bfs2(start,operation); System.out.println(""); } private static void dfsTest(String start) { GraphMatrix<String> graphMatrix = new GraphMatrix<String>(); graphMatrix.insert("A"); graphMatrix.insert("B"); graphMatrix.insert("C"); graphMatrix.insert("D"); graphMatrix.insert("E"); graphMatrix.insert("F"); graphMatrix.insert("G"); graphMatrix.insert("A","B",new Edge(1,1)); graphMatrix.insert("A","F",new Edge(1,2)); graphMatrix.insert("A","C",new Edge(1,3)); graphMatrix.insert("B","C",new Edge(1,4)); graphMatrix.insert("F","G",new Edge(1,5)); graphMatrix.insert("G","C",new Edge(1,6)); graphMatrix.insert("D","A",new Edge(1,7)); graphMatrix.insert("D","E",new Edge(1,8)); graphMatrix.insert("E","F",new Edge(1,9)); graphMatrix.insert("G","A",new Edge(1,10)); VisitedOperation<String> operation = new VisitedOperation<String>() { @Override public void doSomething(Vertex<String> vertex) { System.out.print(" "+vertex.getData()); } }; System.out.print("dfs2結果:"); graphMatrix.dfs2(start, operation); System.out.println(""); } public static void main(String[] args) { bfsTest("S"); dfsTest("A"); dfsTest("D"); }
《圖基礎知識整理和程式碼實現》 )一文中的遍歷結果如圖1所示,本文采用的基於PFS的DFS和BFS執行結果如圖2所示,比較可以發現該演算法是正確的。

圖1.前文的執行結果

圖2.本文的執行結果