吐血整理程序員必讀書單:https://github.com/silently9527/ProgrammerBooksgit
微信公衆號:貝塔學Java程序員
在前面兩篇中咱們經過深度優先搜索能夠從圖中找出一條經過頂點v到頂點w的路徑,可是深度優先搜索與頂點的輸入有很大的關係,找出來的路徑也不必定是最短的,一般狀況下咱們不少時候須要找出圖中的最短路徑,好比:地圖功能。這裏咱們就須要使用到廣度優先搜索算法github
依然使用以前定義的尋找路徑的API算法
public class Paths { Paths(Graph graph, int s); boolean hasPathTo(int v); //判斷出從s->v是否存在路徑 Iterable<Integer> pathTo(int v); //若是存在路徑,返回路徑 }
在廣度優先搜索中,爲了找出最短路徑,咱們須要按照起點的順序來遍歷全部的頂點,而不在是使用遞歸來實現;算法的思路:數據庫
在該算法中,爲了保存路徑,咱們依然須要使用一個邊的數組edgeTo[],用一顆父鏈樹來表示根節點到全部連通頂點的最短路徑。數組
public class BreadthFirstPaths { private boolean marked[]; private int[] edgeTo; private int s; private Queue<Integer> queue = new LinkedListQueue<>(); public BreadthFirstPaths(Graph graph, int s) { this.s = s; this.marked = new boolean[graph.V()]; this.edgeTo = new int[graph.V()]; bfs(graph, s); } private void bfs(Graph graph, int s) { this.marked[s] = true; this.queue.enqueue(s); while (!this.queue.isEmpty()) { Integer v = this.queue.dequeue(); for (int w : graph.adj(v)) { if (!this.marked[w]) { this.marked[w] = true; this.edgeTo[w] = v; this.queue.enqueue(w); } } } } public boolean hasPathTo(int v) { return this.marked[v]; } public Iterable<Integer> pathTo(int v) { if (!hasPathTo(v)) { throw new IllegalStateException("s不能到達v"); } Stack<Integer> stack = new LinkedListStack<>(); stack.push(v); while (edgeTo[v] != s) { stack.push(edgeTo[v]); v = edgeTo[v]; } stack.push(s); return stack; } }
如下圖爲列,來看看廣度優先搜索的運行軌跡微信
單元測試的代碼:單元測試
@Test public void test() { Graph graph = new Graph(8); graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(0, 5); graph.addEdge(1, 3); graph.addEdge(2, 4); graph.addEdge(4, 3); graph.addEdge(5, 3); graph.addEdge(6, 7); BreadthFirstPaths paths = new BreadthFirstPaths(graph, 0); System.out.println(paths.hasPathTo(5)); System.out.println(paths.hasPathTo(2)); System.out.println(paths.hasPathTo(6)); paths.pathTo(5).forEach(System.out::print); System.out.println(); paths.pathTo(4).forEach(System.out::print); System.out.println(); paths.pathTo(2).forEach(System.out::print); System.out.println(); paths.pathTo(3).forEach(System.out::print); }
執行的結果與咱們分析的運行軌跡一致學習
最近幾篇文章一塊兒學習到的圖算法都是以數字做爲了頂點,是由於數字來實現這些算法會很是的簡潔方便,可是在實際的場景中,一般都是使用的字符做爲頂點而非數字,好比:地圖上的位置、電影與演員的關係。測試
爲了知足實際的場景,咱們只須要在數字與字符串的關係作一個映射,此時咱們會想到以前文章實現的map(經過二叉樹實現map、紅黑樹實現map、哈希表實現map等等,有興趣的同窗能夠去翻翻),使用Map來維護字符串和數字的映射關係。
public interface SymbolGraph { boolean contains(String key); //判斷是否存在頂點 int index(String key); //經過名稱返回對應的數字頂點 String name(int v); //經過數字頂點返回對應的字符名稱 Graph graph(); }
實現的思路:
假設構造圖的頂點與邊是經過字符串來表示的,如:a,b,c,d\nb,a,h,l,p\ng,s,z
使用\n分隔的每段第一個字符串表示頂點v,後面的表示與頂點v相連的相鄰頂點;
實際的過程能夠根據具體狀況來肯定,不必定非得這種字符串,能夠來源於數據庫,也能夠來源於網路的請求。
代碼實現以下:
public class SymbolGraph { private Map<String, Integer> map = new RedBlack23RedBlackTreeMap<>(); private String[] keys; private Graph graph; public SymbolGraph(String text) { Arrays.stream(text.split("\n")).forEach(line -> { String[] split = line.split(","); for (int i = 0; i < split.length; i++) { map.put(split[i], i); } }); this.keys = new String[map.size()]; map.keys().forEach(key -> { this.keys[this.map.get(key)] = key; }); this.graph = new Graph(this.keys.length); Arrays.stream(text.split("\n")).forEach(line -> { String[] split = line.split(","); int v = this.map.get(split[0]); for (int i = 1; i < split.length; i++) { this.graph.addEdge(v, this.map.get(split[i])); } }); } public boolean contains(String key) { return map.contains(key); } public int index(String key) { return map.get(key); } public String name(int index) { return this.keys[index]; } public Graph graph() { return this.graph; } public static void main(String[] args) { System.out.println(Arrays.toString("323\n2323".split("\n"))); } }
文中全部源碼已放入到了github倉庫:
https://github.com/silently9527/JavaCore
文中或許會存在或多或少的不足、錯誤之處,有建議或者意見也很是歡迎你們在評論交流。
最後,寫做不易,請不要白嫖我喲,但願朋友們能夠點贊評論關注三連,由於這些就是我分享的所有動力來源🙏