[編程題]比較重量 使用Floyd 最短路算法

[編程題]比較重量
小明陪小紅去看鉆石亏吝,他們從一堆鉆石中隨機抽取兩顆并比較她們的重量掠哥。這些鉆石的重量各不相同。在他們們比較了一段時間后腮介,它們看中了兩顆鉆石g1和g2。現(xiàn)在請你根據(jù)之前比較的信息判斷這兩顆鉆石的哪顆更重端衰。
給定兩顆鉆石的編號g1,g2叠洗,編號從1開始,同時給定關系數(shù)組vector,其中元素為一些二元組旅东,第一個元素為一次比較中較重的鉆石的編號灭抑,第二個元素為較輕的鉆石的編號。最后給定之前的比較次數(shù)n抵代。請返回這兩顆鉆石的關系腾节,若g1更重返回1,g2更重返回-1荤牍,無法判斷返回0案腺。輸入數(shù)據(jù)保證合法,不會有矛盾情況出現(xiàn)康吵。
測試樣例:

2,3,[[1,2],[2,4],[1,3],[4,3]],4

返回: 1

只有五行的 Floyd 最短路算法


暑假救湖,小哼準備去一些城市旅游。有些城市之間有公路涎才,有些城市之間則沒有鞋既,如下圖力九。為了節(jié)省經(jīng)費以及方便計劃旅程,小哼希望在出發(fā)之前知道任意兩個城市之前的最短路程邑闺。



上圖中有 4 個城市 8 條公路跌前,公路上的數(shù)字表示這條公路的長短。請注意這些公路是單向的陡舅。我們現(xiàn)在需要求任意兩個城市之間的最短路程抵乓,也就是求任意兩個點之間的最短路徑。這個問題這也被稱為“多源最短路徑”問題靶衍。

現(xiàn)在需要一個數(shù)據(jù)結構來存儲圖的信息灾炭,我們仍然可以用一個 4*4 的矩陣(二維數(shù)組 e)來存儲。比如 1 號城市到 2 號城市的路程為 2颅眶,則設 e[1][2]的值為 2蜈出。2 號城市無法到達 4 號城市,則設置 e[2][4]的值為 ∞涛酗。另外此處約定一個城市自己是到自己的也是 0铡原,例如 e[1][1]為 0,具體如下商叹。



現(xiàn)在回到問題:如何求任意兩點之間最短路徑呢燕刻?通過之前的學習我們知道通過深度或廣度優(yōu)先搜索可以求出兩點之間的最短路徑。所以進行 n2 遍深度或廣度優(yōu)先搜索剖笙,即對每兩個點都進行一次深度或廣度優(yōu)先搜索卵洗,便可以求得任意兩點之間的最短路徑∶诌洌可是還有沒有別的方法呢过蹂?

我們來想一想,根據(jù)我們以往的經(jīng)驗酪夷,如果要讓任意兩點(例如從頂點 a 點到頂點 b)之間的路程變短,只能引入第三個點(頂點 k)孽惰,并通過這個頂點 k 中轉即 a->k->b晚岭,才可能縮短原來從頂點 a 點到頂點 b 的路程。那么這個中轉的頂點 k 是 1~n 中的哪個點呢勋功?甚至有時候不只通過一個點坦报,而是經(jīng)過兩個點或者更多點中轉會更短,即 a->k1->k2b->或者 a->k1->k2…->k->i…->b狂鞋。比如上圖中從 4 號城市到 3 號城市(4->3)的路程 e[4][3]原本是 12片择。如果只通過 1 號城市中轉(4->1->3),路程將縮短為 11(e[4][1]+e[1][3]=5+6=11)骚揍。其實 1 號城市到 3 號城市也可以通過 2 號城市中轉字管,使得 1 號到 3 號城市的路程縮短為 5(e[1][2]+e[2][3]=2+3=5)啰挪。所以如果同時經(jīng)過 1 號和 2 號兩個城市中轉的話,從 4 號城市到 3 號城市的路程會進一步縮短為 10嘲叔。通過這個的例子亡呵,我們發(fā)現(xiàn)每個頂點都有可能使得另外兩個頂點之間的路程變短。好硫戈,下面我們將這個問題一般化锰什。

當任意兩點之間不允許經(jīng)過第三個點時,這些城市之間最短路程就是初始路程丁逝,如下汁胆。



如現(xiàn)在只允許經(jīng)過 1 號頂點,求任意兩點之間的最短路程霜幼,應該如何求呢嫩码?只需判斷 e[i][1]+e[1][j]是否比 e[i][j]要小即可。e[i][j]表示的是從 i 號頂點到 j 號頂點之間的路程辛掠。e[i][1]+e[1][j]表示的是從 i 號頂點先到 1 號頂點谢谦,再從 1 號頂點到 j 號頂點的路程之和。其中 i 是 1~n 循環(huán)萝衩,j 也是 1~n 循環(huán)回挽,代碼實現(xiàn)如下。

for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            if ( e[i][j] > e[i][1]+e[1][j] )
              e[i][j] = e[i][1]+e[1][j];
        }
    }

在只允許經(jīng)過 1 號頂點的情況下猩谊,任意兩點之間的最短路程更新為:



通過上圖我們發(fā)現(xiàn):在只通過 1 號頂點中轉的情況下千劈,3 號頂點到 2 號頂點(e[3][2])、4 號頂點到 2 號頂點(e[4][2])以及 4 號頂點到 3 號頂點(e[4][3])的路程都變短了牌捷。

接下來繼續(xù)求在只允許經(jīng)過 1 和 2 號兩個頂點的情況下任意兩點之間的最短路程墙牌。如何做呢?我們需要在只允許經(jīng)過 1 號頂點時任意兩點的最短路程的結果下暗甥,再判斷如果經(jīng)過 2 號頂點是否可以使得 i 號頂點到 j 號頂點之間的路程變得更短喜滨。即判斷 e[i][2]+e[2][j]是否比 e[i][j]要小,代碼實現(xiàn)為如下撤防。

//經(jīng)過1號頂點
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if (e[i][j] > e[i][1]+e[1][j])  e[i][j]=e[i][1]+e[1][j];
    //經(jīng)過2號頂點
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if (e[i][j] > e[i][2]+e[2][j])  e[i][j]=e[i][2]+e[2][j];

在只允許經(jīng)過 1 和 2 號頂點的情況下虽风,任意兩點之間的最短路程更新為:



通過上圖得知,在相比只允許通過 1 號頂點進行中轉的情況下寄月,這里允許通過 1 和 2 號頂點進行中轉辜膝,使得 e[1][3]和 e[4][3]的路程變得更短了。

同理漾肮,繼續(xù)在只允許經(jīng)過 1厂抖、2 和 3 號頂點進行中轉的情況下,求任意兩點之間的最短路程克懊。任意兩點之間的最短路程更新為:



最后允許通過所有頂點作為中轉忱辅,任意兩點之間最終的最短路程為:



整個算法過程雖然說起來很麻煩七蜘,但是代碼實現(xiàn)卻非常簡單,核心代碼只有五行:
for(k=1;k<=n;k++)
       for(i=1;i<=n;i++)
           for(j=1;j<=n;j++)
               if(e[i][j]>e[i][k]+e[k][j])
                    e[i][j]=e[i][k]+e[k][j];

這段代碼的基本思想就是:最開始只允許經(jīng)過 1 號頂點進行中轉耕蝉,接下來只允許經(jīng)過 1 和 2 號頂點進行中轉……允許經(jīng)過 1~n 號所有頂點進行中轉崔梗,求任意兩點之間的最短路程。用一句話概括就是:從 i 號頂點到 j 號頂點只經(jīng)過前k號點的最短路程垒在。其實這是一種“動態(tài)規(guī)劃”的思想蒜魄,關于這個思想我們將在《啊哈!算法 2——偉大思維閃耀時》在做詳細的討論场躯。下面給出這個算法的完整代碼:
這段代碼的基本思想就是:最開始只允許經(jīng)過 1 號頂點進行中轉谈为,接下來只允許經(jīng)過 1 和 2 號頂點進行中轉……允許經(jīng)過 1~n 號所有頂點進行中轉,求任意兩點之間的最短路程踢关。用一句話概括就是:從 i 號頂點到 j 號頂點只經(jīng)過前k號點的最短路程伞鲫。其實這是一種“動態(tài)規(guī)劃”的思想,關于這個思想我們將在《啊哈签舞!算法 2——偉大思維閃耀時》在做詳細的討論秕脓。下面給出這個算法的完整代碼:


    #include <stdio.h>
    int main()
    {
        int e[10][10],k,i,j,n,m,t1,t2,t3;
        int inf=99999999; //用inf(infinity的縮寫)存儲一個我們認為的正無窮值
        //讀入n和m,n表示頂點個數(shù),m表示邊的條數(shù)
        scanf("%d %d",&n,&m);

        //初始化
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
                if(i==j) e[i][j]=0;
                  else e[i][j]=inf;
        //讀入邊
        for(i=1;i<=m;i++)
        {
            scanf("%d %d %d",&t1,&t2,&t3);
            e[t1][t2]=t3;
        }

        //Floyd-Warshall算法核心語句
        for(k=1;k<=n;k++)
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)
                    if(e[i][j]>e[i][k]+e[k][j] )
                        e[i][j]=e[i][k]+e[k][j];

        //輸出最終的結果
        for(i=1;i<=n;i++)
        {
         for(j=1;j<=n;j++)
            {
                printf("%10d",e[i][j]);
            }
            printf("\n");
        }

        return 0;
    }

有一點需要注意的是:如何表示正無窮。我們通常將正無窮定義為 99999999陋桂,因為這樣即使兩個正無窮相加市咆,其和仍然不超過 int 類型的范圍(C 語言 int 類型可以存儲的最大正整數(shù)是 2147483647)蟹略。在實際應用中最好估計一下最短路徑的上限,只需要設置比它大一點既可以。例如有 100 條邊,每條邊不超過 100 的話拐辽,只需將正無窮設置為 10001 即可。如果你認為正無窮和其它值相加得到一個大于正無窮的數(shù)是不被允許的話擦酌,我們只需在比較的時候加兩個判斷條件就可以了俱诸,請注意下面代碼中帶有下劃線的語句。

//Floyd-Warshall算法核心語句
    for(k=1;k<=n;k++)
       for(i=1;i<=n;i++)
          for(j=1;j<=n;j++)
            if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])
                e[i][j]=e[i][k]+e[k][j];

上面代碼的輸入數(shù)據(jù)樣式為:

4 8
    1 2 2
    1 3 6
    1 4 4
    2 3 3
    3 1 7
    3 4 1
    4 1 5
    4 3 12

第一行兩個數(shù)為 n 和 m赊舶,n 表 示頂點個數(shù)睁搭,m 表示邊的條數(shù)。
接下來 m 行锯岖,每一行有三個數(shù) t1介袜、t2 和 t3甫何,表示頂點 t1 到頂點 t2 的路程是 t3出吹。
得到最終結果如下:



通過這種方法我們可以求出任意兩個點之間最短路徑。它的時間復雜度是 O(N3)辙喂。令人很震撼的是它竟然只有五行代碼捶牢,實現(xiàn)起來非常容易鸠珠。正是因為它實現(xiàn)起來非常容易,如果時間復雜度要求不高秋麸,使用 Floyd-Warshall 來求指定兩點之間的最短路或者指定一個點到其余各個頂點的最短路徑也是可行的渐排。當然也有更快的算法,請看下一節(jié):Dijkstra 算法灸蟆。

另外需要注意的是:Floyd-Warshall 算法不能解決帶有“負權回路”(或者叫“負權環(huán)”)的圖驯耻,因為帶有“負權回路”的圖沒有最短路。例如下面這個圖就不存在 1 號頂點到 3 號頂點的最短路徑炒考。因為 1->2->3->1->2->3->…->1->2->3 這樣路徑中可缚,每繞一次 1->-2>3 這樣的環(huán),最短路就會減少 1斋枢,永遠找不到最短路帘靡。其實如果一個圖中帶有“負權回路”那么這個圖則沒有最短路。


此算法由 Robert W. Floyd(羅伯特·弗洛伊德)于 1962 年發(fā)表在“Communications of the ACM”上瓤帚。同年 Stephen Warshall(史蒂芬·沃舍爾)也獨立發(fā)表了這個算法描姚。Robert W.Floyd 這個牛人是朵奇葩,他原本在芝加哥大學讀的文學戈次,但是因為當時美國經(jīng)濟不太景氣轩勘,找工作比較困難,無奈之下到西屋電氣公司當了一名計算機操作員朝扼,在 IBM650 機房值夜班赃阀,并由此開始了他的計算機生涯。此外他還和 J.W.J. Williams(威廉姆斯)于 1964 年共同發(fā)明了著名的堆排序算法 HEAPSORT擎颖。堆排序算法我們將在第七章學習榛斯。Robert W.Floyd 在 1978 年獲得了圖靈獎。
【一周一算法】算法 6:只有五行的 Floyd 最短路算法http://bbs.ahalei.com/thread-4554-1-1.html
(出處: 啊哈磊_編程從這里起步)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末搂捧,一起剝皮案震驚了整個濱河市驮俗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌允跑,老刑警劉巖王凑,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異聋丝,居然都是意外死亡索烹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進店門弱睦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來百姓,“玉大人,你說我怎么就攤上這事况木±萋#” “怎么了旬迹?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長求类。 經(jīng)常有香客問我奔垦,道長,這世上最難降的妖魔是什么尸疆? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任椿猎,我火速辦了婚禮,結果婚禮上寿弱,老公的妹妹穿的比我還像新娘鸵贬。我一直安慰自己,他們只是感情好脖捻,可當我...
    茶點故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布阔逼。 她就那樣靜靜地躺著,像睡著了一般地沮。 火紅的嫁衣襯著肌膚如雪嗜浮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天摩疑,我揣著相機與錄音危融,去河邊找鬼。 笑死雷袋,一個胖子當著我的面吹牛吉殃,可吹牛的內容都是我干的。 我是一名探鬼主播楷怒,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蛋勺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸠删?” 一聲冷哼從身側響起抱完,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刃泡,沒想到半個月后巧娱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡烘贴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年禁添,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨踪。...
    茶點故事閱讀 38,694評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡老翘,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情酪捡,我是刑警寧澤,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布纳账,位于F島的核電站逛薇,受9級特大地震影響,放射性物質發(fā)生泄漏疏虫。R本人自食惡果不足惜永罚,卻給世界環(huán)境...
    茶點故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卧秘。 院中可真熱鬧呢袱,春花似錦、人聲如沸翅敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚯涮。三九已至治专,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遭顶,已是汗流浹背张峰。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棒旗,地道東北人喘批。 一個月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像铣揉,于是被迫代替她去往敵國和親饶深。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,580評論 2 349

推薦閱讀更多精彩內容