圖算法系列之深度優(yōu)先搜索(一)

吐血整理程序員必讀書單:https://github.com/silently9527/ProgrammerBooks

微信公眾號:貝塔學(xué)Java

前言

在上一篇中我們把圖通過鄰接表數(shù)組表示出來了泉手,這個(gè)數(shù)據(jù)結(jié)構(gòu)將會做我們實(shí)現(xiàn)圖算法的基礎(chǔ)礁遣,本篇我們將一起開始學(xué)習(xí)圖算法的第一個(gè)搜索算法 - 深度優(yōu)先搜索

搜索API的定義

public class Search {
    Search(Graph graph, int s);

    boolean marked(int v);
    
    int count();
}

在開始實(shí)現(xiàn)算法之前吧秕,我們依然先來定義搜索的API

  1. 構(gòu)造方法提供了一個(gè)圖對象蟆盐,以及一個(gè)起點(diǎn)s勋拟,需要找到與s連通的所有頂點(diǎn)
  2. marked判斷頂點(diǎn)s與v是否相鄰
  3. count返回與頂點(diǎn)s相連的總頂點(diǎn)數(shù)

深度優(yōu)先搜索

image

假如上圖是一個(gè)迷宮括荡,我們需要從頂點(diǎn)0開始找到一條出路疏遏,假設(shè)我們有一條無限長的繩子揍障,和一支粉筆目养,那么可以這樣考慮找到出路:

  1. 先選擇一條通道走,在走的路上放上一根繩子
  2. 每遇到一個(gè)路口就用筆標(biāo)記一下毒嫡,繼續(xù)選擇一條未走過的通道
  3. 當(dāng)遇到一個(gè)已經(jīng)被標(biāo)記的路口時(shí)就退回到上一個(gè)路口繼續(xù)選擇一個(gè)未走過的通道
  4. 當(dāng)回退的路口已經(jīng)沒有路可以走的時(shí)候就在繼續(xù)往后回退

這種方式繩子總能幫你找到一條出路癌蚁,而標(biāo)記不會讓你重復(fù)走已經(jīng)走過的通道。

深度優(yōu)先搜索的實(shí)現(xiàn)思路就和走迷宮的方式一樣;

public class DepthFirstSearch {
    private boolean marked[]; 
    private int count;

    public DepthFirstSearch(Graph graph, int s) {
        this.marked = new boolean[graph.V()];
        this.dfs(graph, s);
    }

    private void dfs(Graph graph, int v) {
        marked[v] = true;
        count++;
        for (int w : graph.adj(v)) {
            if (!marked[w]) {
                dfs(graph, w);
            }
        }
    }

    @Override
    public boolean marked(int v) {
        return marked[v];
    }

    @Override
    public int count() {
        return count;
    }
}

在搜索一張圖的時(shí)候努释,使用遞歸來遍歷所有的頂點(diǎn)碘梢,在訪問其中一個(gè)頂點(diǎn)時(shí):

  1. 標(biāo)記它被已經(jīng)訪問
  2. 遞歸的訪問與之相連的所有鄰接點(diǎn)

單元測試:
構(gòu)建下面這張圖,然后測試深度優(yōu)先搜索

image
@Test
public void test() {
    Graph graph = new Graph(8); //構(gòu)建一張圖
    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); //為了展示

    SeDepthFirstSearcharch search = new DepthFirstSearch(graph, 0);
    System.out.println(search.count());
    System.out.println(search.marked(6));
    System.out.println(search.marked(7));
    System.out.println(search.marked(2));
    System.out.println(search.marked(5));
}

image

尋找路徑的API

以上的遞歸算法只是一個(gè)開始伐蒂,從上面的結(jié)果我們可以看出煞躬,我們只能判斷出哪些頂點(diǎn)與起點(diǎn)s是連通的,無法給出具體的路徑出來逸邦;換句話說恩沛,我們需要實(shí)現(xiàn)從頂點(diǎn)s到頂點(diǎn)v是否存在路徑可達(dá),如果存在請打印出來

public class Paths {
    Paths(Graph graph, int s);
    
    boolean hasPathTo(int v); //判斷出從s->v是否存在路徑
    
    Iterable<Integer> pathTo(int v); //如果存在路徑缕减,返回路徑
}

基于深度優(yōu)先搜索查找圖中的可達(dá)路徑

image

我們依然基于這張圖來看雷客,由于我們需要找出可達(dá)的路徑,所以我們在進(jìn)行搜索的時(shí)候需要記錄下圖中的邊桥狡,這里我們使用的是一個(gè)數(shù)組edgeTo[]搅裙,如果存在一條邊是v->w,那么可以表示成edgeTo[w]=v总放,在深度搜索完成之后這個(gè)edgeTo[]數(shù)組就是一顆由父鏈表示的一顆樹
(父鏈樹在之前的文章中也使用過《如何檢測社交網(wǎng)絡(luò)中兩個(gè)人是否是朋友關(guān)系(union-find算法)》

public class DepthFirstPaths {
    private boolean marked[];
    private int[] edgeTo;
    private int s;

    DepthFirstPaths(Graph graph, int s) {
        this.s = s;
        this.marked = new boolean[graph.V()];
        this.edgeTo = new int[graph.V()];
        this.dfs(graph, s);
    }

    private void dfs(Graph graph, int v) {
        this.marked[v] = true;
        for (int w : graph.adj(v)) {
            if (!marked[w]) {
                this.edgeTo[w] = v;
                this.dfs(graph, w);
            }
        }
    }

    public boolean hasPathTo(int v) {
        return marked[v];
    }

    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            throw new IllegalStateException("s不能到達(dá)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;
    }
}

畫圖來詳細(xì)跟蹤深度優(yōu)先搜索的運(yùn)行軌跡,記錄了edgeTo的變化以及父鏈樹的逐漸形成

image
image
image

最終父鏈樹形成了好爬,接下來我們來寫單元測試校驗(yàn)下生成的父鏈樹和實(shí)際的運(yùn)行結(jié)果是否一致

@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);

    DepthFirstPaths paths = new DepthFirstPaths(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);


}

驗(yàn)證結(jié)果完全匹配了父鏈樹

image

文中所有源碼已放入到了github倉庫:
https://github.com/silently9527/JavaCore

最后(點(diǎn)關(guān)注局雄,不迷路)

文中或許會存在或多或少的不足、錯(cuò)誤之處存炮,有建議或者意見也非常歡迎大家在評論交流炬搭。

最后,寫作不易穆桂,請不要白嫖我喲宫盔,希望朋友們可以點(diǎn)贊評論關(guān)注三連,因?yàn)檫@些就是我分享的全部動(dòng)力來源??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末享完,一起剝皮案震驚了整個(gè)濱河市灼芭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌般又,老刑警劉巖彼绷,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茴迁,居然都是意外死亡寄悯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門堕义,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猜旬,“玉大人,你說我怎么就攤上這事∪鞑粒” “怎么了椿争?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秘遏。 經(jīng)常有香客問我丘薛,道長,這世上最難降的妖魔是什么邦危? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任洋侨,我火速辦了婚禮,結(jié)果婚禮上倦蚪,老公的妹妹穿的比我還像新娘希坚。我一直安慰自己,他們只是感情好陵且,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布裁僧。 她就那樣靜靜地躺著,像睡著了一般慕购。 火紅的嫁衣襯著肌膚如雪聊疲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天沪悲,我揣著相機(jī)與錄音获洲,去河邊找鬼。 笑死殿如,一個(gè)胖子當(dāng)著我的面吹牛贡珊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涉馁,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼门岔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烤送?” 一聲冷哼從身側(cè)響起寒随,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帮坚,沒想到半個(gè)月后牢裳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叶沛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年蒲讯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灰署。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡判帮,死狀恐怖局嘁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晦墙,我是刑警寧澤悦昵,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站晌畅,受9級特大地震影響但指,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抗楔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一棋凳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧连躏,春花似錦剩岳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至勺良,卻和暖如春绰播,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尚困。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工蠢箩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尾组。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓忙芒,卻偏偏與公主長得像示弓,于是被迫代替她去往敵國和親讳侨。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

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