本文介紹使用java.util.*包中的HashMap 和 LinkedList 以及 ArrayList類快速實現一個有向圖,並實現有向圖的深度優先遍歷算法。
如何構造圖?
本文根據字符串數組來構造一個圖。圖的頂點標識用字符串來表示,如果某個字符串A的第一個字符與另一個字符串B的最后一個字符相同,則它們之間構造一條有向邊<A,B>。比如,字符串數組{"hap","peg","pmg","god"}對應的有向圖如下:
采用圖的鄰接表 表示法 來實現圖。使用Map<String, ArrayList<String>>來代表圖的鄰接表。
Key 表示頂點的標識,如,"hap","peg"....
Value 表示頂點的鄰接表。如,頂點"hap"的鄰接表是 <"pmg","peg">
因此,這里就用一個簡單的Map來實現了圖。而不是像這篇文章:數據結構--圖 的JAVA實現(上) 中那樣,定義頂點類Vertex.java,邊類Edge.java。
構造圖的具體代碼思路如下:先初始化整個圖,將每個頂點添加到圖中,並初始化它們的鄰接表。
1 Map<String, ArrayList<String>> graph = new HashMap<String, ArrayList<String>>(arr.length);//根據字符串數組arr構造圖 2 3 for (String str : arr) { 4 graph.put(str, new ArrayList<String>()); 5 }
對字符串數組中的每個字符串,遍歷數組中的其他字符串,判斷:某個字符串A的第一個字符與另一個字符串B的最后一個字符 是否相同。若相同,則將字符串B添加到字符串A的鄰接表中去。
1 if(start.charAt(startLen-1) == end.charAt(0))//start-->end 2 { 3 adjs = graph.get(start); 4 if(!adjs.contains(end)) 5 adjs.add(end); 6 graph.put(start, adjs); 7 }else if(start.charAt(0) == end.charAt(endLen-1)){//end-->start 8 adjs = graph.get(end); 9 if(!adjs.contains(start)) 10 adjs.add(start); 11 graph.put(end, adjs); 12 }
圖的深度優先遍歷算法實現
這里實現非遞歸DFS遍歷。用一個LinkedList<String> stack 來模擬遞歸DFS時用到的棧。用一個HashSet<String>來標記某個頂點是否訪問了,如果該頂點被訪問了,則添加到HashSet<String>中。用一個ArrayList<String>來保存DFS遍歷時經過的頂點路徑,最終函數返回該ArrayList<String>表示本次調用DFS遍歷得到的訪問路徑。
1 ArrayList<String> paths = new ArrayList<>(graph.size());//保存訪問路徑 2 3 HashSet<String> visited = new HashSet<>(graph.size());//用來判斷某個頂點是否已經訪問了 4 LinkedList<String> stack = new LinkedList<>();//模擬遞歸遍歷中的棧 5 6 stack.push(start); 7 paths.add(start); 8 visited.add(start);
深度優先遍歷的思路是:
先將起始頂點入棧--->獲取棧頂元素作為當前正在遍歷的元素--->獲得當前正在遍歷的元素的鄰接表--->找出它的鄰接表中還未被訪問的一個頂點--->訪問該頂點(將該頂點保存到訪問路徑中),並將該頂點壓棧
如果當前正在遍歷的元素的鄰接表為空或者該頂點的所有鄰接表中的頂點都已經訪問了,說明:需要回退了。因此,彈出棧頂元素。
1 while(!stack.isEmpty()) 2 { 3 String next = null;//下一個待遍歷的頂點 4 String currentVertex = stack.peek();//當前正在遍歷的頂點 5 ArrayList<String> adjs = graph.get(currentVertex);//獲取當前頂點的鄰接表 6 if(adjs != null) 7 { 8 for (String vertex : adjs) { 9 if(!visited.contains(vertex))//vertex 未被訪問過 10 { 11 next = vertex; 12 break; 13 } 14 } 15 }//end if 16 17 if(next != null)//當前頂點還有未被訪問的鄰接點 18 { 19 paths.add(next);//將該鄰接點添加到訪問路徑中 20 stack.push(next); 21 visited.add(next); 22 }else{ 23 stack.pop();//回退 24 } 25 }//end while
給定一個有向圖,如何判斷:一定存在着某個頂點,從該頂點進行DFS遍歷,能夠遍歷完圖中所有的頂點?
比如上圖,從"hap"頂點出發,進行DFS遍歷,能夠遍歷完圖中所有的頂點。
而從"pmg"頂點出發,進行DFS遍歷,只能從"pmg"訪問到"god"頂點。不能遍歷完圖中所有的頂點。
思路很簡單:只要將 每次從某個頂點出發開始遍歷的路徑 ArrayList<String> 中的元素個數 與 圖中頂點個數比較。如果相等,則說明能夠遍歷完圖中所有的頂點,否則不能。
1 //從圖中的每一個頂點開始DFS遍歷 2 for (String v : vertexs) { 3 paths = dfs(graph, v); 4 5 if(paths.size() == graph.size())//從 頂點 v 遍歷 能夠遍歷完圖中所有的頂點. 6 { 7 System.out.println("從頂點: " + v + " 開始DFS遍歷能夠遍歷完所有的頂點,路徑如下:"); 8 printPath(paths, graph); 9 result = true; 10 break; 11 } 12 }
另:補充一下DFS的遞歸實現:
1 public static ArrayList<String> dfs_recu(String start, Map<String, ArrayList<String>> graph) 2 { 3 ArrayList<String> paths = new ArrayList<>();//保存訪問路徑 4 HashSet<String> visited = new HashSet<>();//保存頂點的訪問狀態 5 dfsrecuresive(start, graph, paths, visited);//遞歸DFS 6 return paths;//返回本次DFS的訪問路徑 7 } 8 private static void dfsrecuresive(String start, Map<String, ArrayList<String>> graph, ArrayList<String> paths, HashSet<String> visited) 9 { 10 visited.add(start);//標記 start 已訪問 11 paths.add(start);//將 start 添加到路徑中 12 13 ArrayList<String> adjs = graph.get(start); 14 for (String v : adjs) { 15 if(!visited.contains(v))//如果 start頂點的鄰接表中還有未被訪問的頂點 16 dfsrecuresive(v, graph, paths, visited);//遞歸訪問該未被訪問的頂點v 17 } 18 }
總結:使用這種方式而不是“面向對象”的方式(定義頂點類、邊類)來實現圖,並實現圖的DFS算法,感覺是空間復雜度有點大。比如,DFS遍歷過程中需要判斷某個頂點是否已經訪問了,上面的處理是:當某頂點已經訪問了時,將之加入到HashSet中。HashSet就是O(N)的空間復雜度。
但是,這種方式編程實現快(個人感覺,哈哈)。
整個程序完整代碼實現:

import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; /** * 有向圖的深度優先遍歷實現 在深度遍歷中,是否存在一條路徑包含了圖中所有的頂點?? * * @author psj * */ public class DFSOrder { public static Map<String, ArrayList<String>> buildGraph(String[] arr) { Map<String, ArrayList<String>> graph = new HashMap<String, ArrayList<String>>( arr.length); for (String str : arr) { graph.put(str, new ArrayList<String>()); } String start; int startLen; String end; int endLen; for (int i = 0; i < arr.length; i++) { start = arr[i]; startLen = start.length(); if (startLen == 0) continue; ArrayList<String> adjs = null; for (int j = 0; j < arr.length; j++) { end = arr[j]; endLen = end.length(); if (endLen == 0) continue; if (start.charAt(startLen - 1) == end.charAt(0))// start-->end { adjs = graph.get(start); if (!adjs.contains(end)) adjs.add(end); graph.put(start, adjs); } else if (start.charAt(0) == end.charAt(endLen - 1)) {// end-->start adjs = graph.get(end); if (!adjs.contains(start)) adjs.add(start); graph.put(end, adjs); } } } return graph; } /** * 從start頂點開始,對graph進行DFS遍歷(非遞歸) * * @param graph * @param start * @return DFS遍歷順序 */ public static ArrayList<String> dfs(Map<String, ArrayList<String>> graph, String start) { assert graph.keySet().contains(start);// 假設 start 一定是圖中的頂點 ArrayList<String> paths = new ArrayList<>(graph.size()); HashSet<String> visited = new HashSet<>(graph.size());// 用來判斷某個頂點是否已經訪問了 LinkedList<String> stack = new LinkedList<>();// 模擬遞歸遍歷中的棧 stack.push(start); paths.add(start); visited.add(start); while (!stack.isEmpty()) { String next = null;// 下一個待遍歷的頂點 String currentVertex = stack.peek();// 當前正在遍歷的頂點 ArrayList<String> adjs = graph.get(currentVertex);// 獲取當前頂點的鄰接表 if (adjs != null) { for (String vertex : adjs) { if (!visited.contains(vertex))// vertex 未被訪問過 { next = vertex; break; } } }// end if if (next != null)// 當前頂點還有未被訪問的鄰接點 { paths.add(next);// 將該鄰接點添加到訪問路徑中 stack.push(next); visited.add(next); } else { stack.pop();// 回退 } }// end while return paths; } // 打印從某個頂點開始的深度優先遍歷路徑 public static void printPath(ArrayList<String> paths, Map<String, ArrayList<String>> graph) { System.out.println("dfs path:"); for (String v : paths) { System.out.print(v + " "); } System.out.println(); } // 判斷有向圖中是否存在某頂點,從該頂點進行DFS遍歷,能夠遍歷到圖中所有的頂點 public static boolean containsAllNode(Map<String, ArrayList<String>> graph) { boolean result = false; ArrayList<String> paths = null; Set<String> vertexs = graph.keySet(); // 從圖中的每一個頂點開始DFS遍歷 for (String v : vertexs) { paths = dfs(graph, v); if (paths.size() == graph.size())// 從 頂點 v 遍歷 能夠遍歷完圖中所有的頂點. { System.out.println("從頂點: " + v + " 開始DFS遍歷能夠遍歷完所有的頂點,路徑如下:"); printPath(paths, graph); result = true; break; } } return result; } // hapjin test public static void main(String[] args) { // String[] words = {"me","cba","agm","abc","eqm","cde"}; String[] words = { "abc", "cde", "efg", "che" }; Map<String, ArrayList<String>> graph = buildGraph(words); System.out.println(containsAllNode(graph)); } }
來源:hapjin blog