圖論 應(yīng)用篇

上次寫(xiě)了篇圖的基本構(gòu)造方法综芥,運(yùn)用圖這種強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)結(jié)構(gòu)力崇,還能解決實(shí)際應(yīng)用中的許多問(wèn)題爸黄,今天這篇就主要整理一些常見(jiàn)的應(yīng)用

一、路徑問(wèn)題

路徑問(wèn)題在圖的處理領(lǐng)域是非常重要的擎鸠。如我們最常見(jiàn)的走迷宮缀磕,就是典型的尋路問(wèn)題。這里主要運(yùn)用深度優(yōu)先和廣度優(yōu)先算法兩種方式來(lái)進(jìn)行路徑尋找劣光,這2種搜索算法在很多數(shù)據(jù)結(jié)構(gòu)中都有重要的運(yùn)用袜蚕,之前寫(xiě)的一篇二叉查找樹(shù)中的層序遍歷就用到了廣度優(yōu)先算法,這里就詳細(xì)的介紹一下绢涡。

1.深度優(yōu)先尋找路徑

首先是深度優(yōu)先,為了更加形象牲剃,直接看下圖
![][1]

這里以頂點(diǎn)1為出發(fā)點(diǎn),4為終點(diǎn)雄可。假設(shè)一個(gè)人要走到終點(diǎn)凿傅,從1出發(fā)有三條路徑缠犀,首先沿著往5的路徑進(jìn)行遍歷,依次1 -> 5 -> 9 -> 8狭归,然后發(fā)現(xiàn)沒(méi)路了夭坪,就返回上一頂點(diǎn),到頂點(diǎn)5這里發(fā)現(xiàn)還有一條路就繼續(xù)沿著這條路走过椎,5->3->6室梅,結(jié)果又沒(méi)路了,就繼續(xù)返回到起點(diǎn)疚宇,沿著另一條路行走......1->2->4亡鼠,一看,這下就直接到終點(diǎn)了敷待,轉(zhuǎn)了幾條路終于找到了終點(diǎn)间涵,那心里真是無(wú)比的興奮啊榜揖!回到正題勾哩,看看這個(gè)具體實(shí)現(xiàn),可以用一個(gè)boolean類(lèi)型的變量來(lái)標(biāo)記是否遍歷過(guò)該頂點(diǎn)举哟,用一個(gè)int型的變量表示從起點(diǎn)到一個(gè)頂點(diǎn)的已知路徑上的最后一個(gè)頂點(diǎn)思劳。至于圖的基本構(gòu)造可以參考我之前寫(xiě)的[圖論基礎(chǔ)篇][2]

/**
 *  圖的深度優(yōu)先查找路徑
 * @author Legend
 */
public class DepthFirstPaths {

    private boolean[] marked; // 該頂點(diǎn)是否調(diào)用過(guò)dfs
    private int[] edgeTo; // 從起點(diǎn)到一個(gè)頂點(diǎn)的已知路徑上的最后一個(gè)頂點(diǎn)
    private final int s; // 起點(diǎn)
    // 圖的初始化
    public DepthFirstPaths(Graph G,int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        dfs(G,s);
    }
    // 深度優(yōu)先主方法
    private void dfs(Graph G,int v) {
        marked[v] = true;
        // 遍歷與頂點(diǎn)v相連的邊
        for (int w : G.adj(v)) {  
            if (!marked[w]) {
                edgeTo[w] = v;
                dfs(G,w);  // 繼續(xù)遞歸的進(jìn)行遍歷
            }
        }
    }
   // 是否存在s到v的路徑
    public boolean hasPathTo(int v) {
        return marked[v];
    }
   // s到v的路徑
    public Iterable<Integer> PathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<>();
        for (int i = v;i != s;i = edgeTo[i]) {
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

因?yàn)槲覀円獪?zhǔn)確的知道每一條路徑,所以這里創(chuàng)建了一個(gè)edgeTo變量用于記錄從起點(diǎn)到一個(gè)頂點(diǎn)的已知路徑上的最后一個(gè)頂點(diǎn)妨猩,而edgeTo[w] = v表示的就是從w到v的路徑潜叛。再看PathTo方法,首先判斷是否有從s到v的路徑壶硅,沒(méi)有就返回null威兜。然后實(shí)例化一個(gè)Stack類(lèi)型對(duì)象path,依次遍歷庐椒,把路徑上每一個(gè)頂點(diǎn)都push進(jìn)去椒舵,最后在push頂點(diǎn),并返回path约谈。

2.廣度優(yōu)先尋找路徑

廣度優(yōu)先正如其名笔宿,優(yōu)先進(jìn)行廣度的遍歷,整個(gè)過(guò)程呈擴(kuò)散狀窗宇。這里還是用上面那張圖,為了方便查看特纤,還是把圖片放到這里军俊。
![][3]
[1]: http://img.mukewang.com/59fb24c20001780108340606.png
[2]: http://blog.cspojie.cn/2017/10/20/%E5%9B%BE%E8%AE%BA-%E5%9F%BA%E7%A1%80%E7%AF%87/#more,這里直接用Graph來(lái)表示捧存,然后具體實(shí)現(xiàn)看看下面的代碼
[3]: http://img.mukewang.com/59fd753d0001780108340606.png
還是用之前的情景粪躬,從頂點(diǎn)1出發(fā)担败,先遍歷和1相鄰的頂點(diǎn) 2,5,3,然后從頂點(diǎn)2開(kāi)始镰官,右繼續(xù)遍歷和2相鄰的頂點(diǎn)提前,因?yàn)橐呀?jīng)遍歷過(guò)頂點(diǎn)1,所以這里就只需要遍歷頂點(diǎn)7和頂點(diǎn)4泳唠,發(fā)然后現(xiàn)就直接到終點(diǎn)了狈网,這是在特殊的情況下,如果先遍歷的是另外的頂點(diǎn)笨腥,那么幾乎要走完每條路才能找到終點(diǎn)拓哺。這里就不多說(shuō)了,重點(diǎn)講下具體實(shí)現(xiàn)脖母,這里用一個(gè)抽象數(shù)據(jù)結(jié)構(gòu)---隊(duì)列來(lái)實(shí)現(xiàn)士鸥,首先創(chuàng)建一個(gè)隊(duì)列,然后把起點(diǎn)標(biāo)記后插入隊(duì)列中去谆级,如果隊(duì)列不為空就把當(dāng)前的頂點(diǎn)彈出隊(duì)列烤礁,然后依次遍歷和這個(gè)頂點(diǎn)相鄰的頂點(diǎn),并把這些頂點(diǎn)標(biāo)記后也加入隊(duì)列肥照,接下來(lái)用同樣的方式將下一頂點(diǎn)彈出隊(duì)列脚仔,遍歷和其相鄰的頂點(diǎn),直到隊(duì)列為空就停止遍歷建峭。好了玻侥,具體看下面的代碼

/**
 *
 * 廣度優(yōu)先尋找路徑
 * @author Legend
 **/

public class BreadthFirstPaths {

    private boolean[] marked;
    private int[] edgeTo;
    private final int s;
    public BreadthFirstPaths(Graph G,int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        try {
            bfs(G,s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void bfs(Graph G,int s) throws InterruptedException {
        Queue<Integer> queue = new Queue<Integer>();
        marked[s] = true;
        queue.enqueue(s);
        while ( !queue.isEmpty() ) {
            int v = queue.dequeue(); // 從隊(duì)列中彈出一個(gè)頂點(diǎn)
            for (int w : G.adj(v)) { // 遍歷和該頂點(diǎn)相鄰的頂點(diǎn)
                if ( !marked[w] ) {
                    edgeTo[w] = v; // 保存最短路徑的最后一條邊
                    marked[w] = true; // 標(biāo)記它 因?yàn)樽疃搪窂揭阎?                    queue.enqueue(w);
                }
            }
        }
    }
    public boolean hasPathTo(int v) {
        return marked[v];
    }
    public Iterable<Integer> pathTo(int v) {
        if ( !hasPathTo(v) ) {
            return null;
        }
        Stack<Integer> path = new Stack<>();
        for (int i = 0;i != s;i =edgeTo[i] ){
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

這里同樣運(yùn)用了pathTo方法,頂點(diǎn)與路徑的標(biāo)記過(guò)程和深度優(yōu)先尋找路徑是一樣的亿蒸,這里就不多說(shuō)了凑兰。

連通分量問(wèn)題

1.介紹

連通分量也是一個(gè)比較常見(jiàn)的問(wèn)題,主要用于判斷任意兩個(gè)結(jié)點(diǎn)的連接狀態(tài)边锁,特別是用于檢測(cè)網(wǎng)絡(luò)連接與電路連接的問(wèn)題中姑食,運(yùn)用比較廣。

2.基本實(shí)現(xiàn)

也沒(méi)什么好說(shuō)的茅坛,這里還是運(yùn)用深度優(yōu)先的方法音半,比較簡(jiǎn)單,就直接上代碼

/**
 * 使用深度優(yōu)先找出圖中的連通分量
 *
 * @author Legend
 * @create 2017-11-01 8:23
 **/

public class CC {
    private int count;
    private boolean marked[];
    private int[] id;

    public CC(Graph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for (int s = 0;s < G.V();s++) {
            if (!marked[s]) {
                dfs(G,s);
                count++;
            }
        }
    }
    private void dfs(Graph G,int v) {
        marked[v] = true;
        id[v] = count;
        for (int w : G.adj(v)) {
            if (!marked[w]) {
                dfs(G,w);
            }
        }
    }
    // 判斷兩個(gè)結(jié)點(diǎn)是否相連接
    public boolean isConnected(int v,int w) {
        return id[v] == id[w];
    }
    // v所在連通分量的標(biāo)識(shí)符(0~count-1)
    public int id(int v) {
        return id[v];
    }
    // 連通分量的數(shù)量
    public int count() {
        return count;
    }
}

雙色問(wèn)題

1.介紹

能否用2種顏色將圖的所有頂點(diǎn)著色贡蓖,使得任意一條邊的兩個(gè)端點(diǎn)的顏色都不相同,這個(gè)問(wèn)題也就等價(jià)于當(dāng)前的圖是不是二分圖曹鸠。因?yàn)槎鏄?shù)其實(shí)就是一種比較特殊的圖,所以才有這個(gè)問(wèn)題斥铺。

2.基本實(shí)現(xiàn)

具體實(shí)現(xiàn)可以用一個(gè)bool類(lèi)型的變量來(lái)表示2種顏色彻桃,直接在構(gòu)造方法里面進(jìn)行循環(huán),這里也是運(yùn)用深度優(yōu)先的方式晾蜘。首先判斷當(dāng)前結(jié)點(diǎn)是否被遍歷邻眷,沒(méi)被遍歷過(guò)就進(jìn)行遍歷眠屎,也就是用bool型變量將其標(biāo)記為true,然后遍歷和這個(gè)結(jié)點(diǎn)相連的結(jié)點(diǎn)肆饶,并且把這個(gè)結(jié)點(diǎn)和相鄰的結(jié)點(diǎn)涂上不同的顏色改衩。然后進(jìn)行判斷
先來(lái)看下代碼

/**
 * 雙色問(wèn)題
 *
 * @author Legend
 * @create 2017-11-01 9:17
 **/

public class TwoColor {
    private boolean[] marked; //當(dāng)前結(jié)點(diǎn)是否被遍歷過(guò)
    private boolean[] color; // 表示不同顏色
    private boolean isTwoColorable = true; // 是否能用2種顏色表示
    public TwoColor(Graph G) {
        marked = new boolean[G.V()];
        color = new boolean[G.V()];
        for (int s = 0;s < G.V();s++) {
            if (!marked[s]) {
                dfs(G,s);
            }
        }
    }
    private void dfs(Graph G,int v) {
        marked[v] = true;
        for (int w : G.adj(v)) {
            if (!marked[w]) {
                color[w] = !color[v];
                dfs(G,w);
            } else if (color[w] == color[v]) {
                isTwoColorable = false;
            }
        }
    }
    public boolean isBipartite() {
        return isTwoColorable;
    }
}

如果這2個(gè)頂點(diǎn)顏色相同,則不能使得任意一條邊的2個(gè)端點(diǎn)的顏色都不相同驯镊,則這個(gè)圖不是二分圖葫督,試想一下,如果是二分圖阿宅,任意一條的兩個(gè)端點(diǎn)肯定顏色是不相同的候衍。因?yàn)槊看伪闅v時(shí)都將當(dāng)前頂點(diǎn)與連接頂點(diǎn)標(biāo)記了2種不同的顏色,如果這個(gè)頂點(diǎn)有多個(gè)相鄰頂點(diǎn)洒放,并且這些相鄰頂點(diǎn)又有邊相連蛉鹿,這必然會(huì)造成2個(gè)顏色相同,這樣的圖自然也不可能是二分圖了往湿。

因?yàn)檫@篇博客主要是整理圖論的一些應(yīng)用的問(wèn)題妖异,所以對(duì)于這些問(wèn)題的優(yōu)化這里不是重點(diǎn),有興趣的可以自己去查查資料领追,那圖論應(yīng)用篇就暫時(shí)到這里了他膳。
ps:該blog首發(fā)lenged's blog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绒窑,隨后出現(xiàn)的幾起案子棕孙,更是在濱河造成了極大的恐慌,老刑警劉巖些膨,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟀俊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡订雾,警方通過(guò)查閱死者的電腦和手機(jī)肢预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洼哎,“玉大人烫映,你說(shuō)我怎么就攤上這事∝停” “怎么了锭沟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)识补。 經(jīng)常有香客問(wèn)我族淮,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任瞧筛,我火速辦了婚禮,結(jié)果婚禮上导盅,老公的妹妹穿的比我還像新娘较幌。我一直安慰自己,他們只是感情好白翻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布乍炉。 她就那樣靜靜地躺著,像睡著了一般滤馍。 火紅的嫁衣襯著肌膚如雪岛琼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天巢株,我揣著相機(jī)與錄音槐瑞,去河邊找鬼。 笑死阁苞,一個(gè)胖子當(dāng)著我的面吹牛困檩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播那槽,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悼沿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了骚灸?” 一聲冷哼從身側(cè)響起糟趾,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甚牲,沒(méi)想到半個(gè)月后义郑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鳖藕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年魔慷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片著恩。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡院尔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喉誊,到底是詐尸還是另有隱情邀摆,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布伍茄,位于F島的核電站栋盹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏敷矫。R本人自食惡果不足惜例获,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一汉额、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧榨汤,春花似錦蠕搜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蜜宪,卻和暖如春虫埂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背圃验。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工掉伏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澳窑。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓岖免,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親照捡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颅湘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • 第一章 緒論 什么是數(shù)據(jù)結(jié)構(gòu)? 數(shù)據(jù)結(jié)構(gòu)的定義:數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合栗精。 第二章...
    SeanCheney閱讀 5,775評(píng)論 0 19
  • 本文涉及更多的是概念闯参,代碼部分請(qǐng)參考之前寫(xiě)過(guò)的 2 篇博客 基于Javascript的排序算法基本數(shù)據(jù)結(jié)構(gòu)和查找算...
    faremax閱讀 1,242評(píng)論 0 2
  • B樹(shù)的定義 一棵m階的B樹(shù)滿(mǎn)足下列條件: 樹(shù)中每個(gè)結(jié)點(diǎn)至多有m個(gè)孩子。 除根結(jié)點(diǎn)和葉子結(jié)點(diǎn)外悲立,其它每個(gè)結(jié)點(diǎn)至少有m...
    文檔隨手記閱讀 13,222評(píng)論 0 25
  • 圖是一種比線性表和樹(shù)更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)漆弄,在圖中酷誓,結(jié)點(diǎn)之間的關(guān)系是任意的,任意兩個(gè)數(shù)據(jù)元素之間都可能相關(guān)原献。圖是一種多對(duì)...
    Alent閱讀 2,306評(píng)論 1 22
  • 在不同人眼里馏慨,人生有不同的狀態(tài)。樂(lè)觀的人把人生活成一場(chǎng)喜劇姑隅,悲觀的人把人生活成一場(chǎng)悲劇写隶。你很堅(jiān)強(qiáng),那么你就可以支配...
    怪咖習(xí)心閱讀 371評(píng)論 0 0