吐血整理程序員必讀書單:https://github.com/silently9527/ProgrammerBooks
微信公眾號:貝塔學Java
前言
在前面兩篇中我們通過深度優(yōu)先搜索可以從圖中找出一條通過頂點v到頂點w的路徑山上,但是深度優(yōu)先搜索與頂點的輸入有很大的關系滨嘱,找出來的路徑也不一定是最短的嫩痰,通常情況下我們很多時候需要找出圖中的最短路徑,比如:地圖功能笛粘。這里我們就需要使用到廣度優(yōu)先搜索算法
廣度優(yōu)先搜索
依然使用之前定義的尋找路徑的API
public class Paths {
Paths(Graph graph, int s);
boolean hasPathTo(int v); //判斷出從s->v是否存在路徑
Iterable<Integer> pathTo(int v); //如果存在路徑乔妈,返回路徑
}
在廣度優(yōu)先搜索中外莲,為了找出最短路徑队贱,我們需要按照起點的順序來遍歷所有的頂點,而不在是使用遞歸來實現(xiàn)住拭;算法的思路:
- 使用隊列來保存已經(jīng)被標記過但是鄰接表還未被遍歷過的頂點
- 取出隊列中的下一個頂點v并標記它
- 將v相鄰的所有未被標記的頂點加入到隊列
在該算法中挪略,為了保存路徑历帚,我們依然需要使用一個邊的數(shù)組edgeTo[],用一顆父鏈樹來表示根節(jié)點到所有連通頂點的最短路徑杠娱。
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;
}
}
以下圖為列挽牢,來看看廣度優(yōu)先搜索的運行軌跡
單元測試的代碼:
@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);
}
執(zhí)行的結果與我們分析的運行軌跡一致
符號圖
最近幾篇文章一起學習到的圖算法都是以數(shù)字作為了頂點,是因為數(shù)字來實現(xiàn)這些算法會非常的簡潔方便摊求,但是在實際的場景中禽拔,通常都是使用的字符作為頂點而非數(shù)字,比如:地圖上的位置室叉、電影與演員的關系奏赘。
為了滿足實際的場景,我們只需要在數(shù)字與字符串的關系做一個映射太惠,此時我們會想到之前文章實現(xiàn)的map(通過二叉樹實現(xiàn)map、紅黑樹實現(xiàn)map疲憋、哈希表實現(xiàn)map等等凿渊,有興趣的同學可以去翻翻),使用Map來維護字符串和數(shù)字的映射關系缚柳。
public interface SymbolGraph {
boolean contains(String key); //判斷是否存在頂點
int index(String key); //通過名稱返回對應的數(shù)字頂點
String name(int v); //通過數(shù)字頂點返回對應的字符名稱
Graph graph();
}
實現(xiàn)的思路:
- 使用Map來保存字符串-數(shù)字的映射埃脏,key為字符串,value為數(shù)字
- 使用一個數(shù)組來反向映射數(shù)字-字符串秋忙,數(shù)組的下標對應數(shù)字頂點彩掐,值對應字符串名稱
假設構造圖的頂點與邊是通過字符串來表示的,如:a,b,c,d\nb,a,h,l,p\ng,s,z
使用\n分隔的每段第一個字符串表示頂點v灰追,后面的表示與頂點v相連的相鄰頂點堵幽;
實際的過程可以根據(jù)具體情況來確定,不一定非得這種字符串弹澎,可以來源于數(shù)據(jù)庫朴下,也可以來源于網(wǎng)路的請求。
代碼實現(xiàn)如下:
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
最后(點關注苦蒿,不迷路)
文中或許會存在或多或少的不足殴胧、錯誤之處,有建議或者意見也非常歡迎大家在評論交流佩迟。
最后团滥,寫作不易,請不要白嫖我喲报强,希望朋友們可以點贊評論關注三連灸姊,因為這些就是我分享的全部動力來源??