圖算法系列之計算圖中最短路徑

吐血整理程序員必讀書單: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)住拭;算法的思路:

  1. 使用隊列來保存已經(jīng)被標記過但是鄰接表還未被遍歷過的頂點
  2. 取出隊列中的下一個頂點v并標記它
  3. 將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)先搜索的運行軌跡

image
image

單元測試的代碼:

@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í)行的結果與我們分析的運行軌跡一致

image

符號圖

最近幾篇文章一起學習到的圖算法都是以數(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)的思路:

  1. 使用Map來保存字符串-數(shù)字的映射埃脏,key為字符串,value為數(shù)字
  2. 使用一個數(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

最后(點關注苦蒿,不迷路)

文中或許會存在或多或少的不足殴胧、錯誤之處,有建議或者意見也非常歡迎大家在評論交流佩迟。

最后团滥,寫作不易,請不要白嫖我喲报强,希望朋友們可以點贊評論關注三連灸姊,因為這些就是我分享的全部動力來源??

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市躺涝,隨后出現(xiàn)的幾起案子厨钻,更是在濱河造成了極大的恐慌扼雏,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夯膀,死亡現(xiàn)場離奇詭異诗充,居然都是意外死亡,警方通過查閱死者的電腦和手機诱建,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門蝴蜓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人俺猿,你說我怎么就攤上這事茎匠。” “怎么了押袍?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵诵冒,是天一觀的道長。 經(jīng)常有香客問我谊惭,道長汽馋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任圈盔,我火速辦了婚禮豹芯,結果婚禮上,老公的妹妹穿的比我還像新娘驱敲。我一直安慰自己铁蹈,他們只是感情好,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布众眨。 她就那樣靜靜地躺著握牧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪围辙。 梳的紋絲不亂的頭發(fā)上我碟,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機與錄音姚建,去河邊找鬼矫俺。 笑死,一個胖子當著我的面吹牛掸冤,可吹牛的內(nèi)容都是我干的厘托。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼稿湿,長吁一口氣:“原來是場噩夢啊……” “哼铅匹!你這毒婦竟也來了?” 一聲冷哼從身側響起饺藤,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤包斑,失蹤者是張志新(化名)和其女友劉穎流礁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罗丰,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡神帅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萌抵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片找御。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绍填,靈堂內(nèi)的尸體忽然破棺而出霎桅,到底是詐尸還是另有隱情,我是刑警寧澤讨永,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布滔驶,位于F島的核電站,受9級特大地震影響卿闹,放射性物質發(fā)生泄漏瓜浸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一比原、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杠巡,春花似錦量窘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嫩海,卻和暖如春冬殃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叁怪。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工审葬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奕谭。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓涣觉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親血柳。 傳聞我的和親對象是個殘疾皇子官册,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容