有一天晚上,有四個人需要通過架在山穀間的危橋,任意時刻最多隻能有兩個人在橋上,過橋需要一盞閃光燈,這些人只有一盞閃光燈。如果單獨過橋他們分別需要10、5、2、1分鐘,如果兩人同時過橋則所需時間是較慢者所需的時間。18分鐘後,沿山穀滾滾而下的山洪將把這座橋沖毀。這四個人能及時過橋嗎?不用圖論知識,證明你的結論;並說明如何用圖論知識獲得答案。
證明:只有一隻手電筒,並且過橋時必須要用到手電筒,唯一的辦法就是讓2個人一起過橋,然後讓其中的一個在返回來送手電筒,從橋上走一來回的結果是可以讓1個人通過.當2個人一起過橋時,總是最慢的1個人決定過橋的速度。把這幾個人按照花費時間的由多到少編號為D,C,B,A.。D的10分鐘肯定要花費,其他三人只能用7分鐘,為了讓時間最少,D和C肯定得一起過,並且回來的時候不能用C或D,所以
1. 一開始應該讓A和B先過 2分鐘;
2. 然後A回來,讓C和D過去 1+10分鐘;
3. B回來,A和B一起過去 2+2分鐘。
總用時為:2+1+10+2+2=17分鐘,即這四個人在18分鐘內及時能過橋。
用圖論知識,可以以4個人在橋兩端的狀態來作為節點來構造一個有向圖,以已經過橋了的人的狀態作為圖的節點,初始時沒有人過橋,所以以空表示,第一輪有兩個人過橋,有6種可能的組合,(1,2)(1,5)(1,10)(2,5)(2,10)(5,10),從空的狀態轉換到這些狀態的需要的時間分別為2,5,10,5,10,10分鐘,時間就作為有向邊的權值。當有兩個人過橋後,需要一個人拿手電筒回去接其他人,這時有四種可能的情況,分別是1,2,5,10中的一人留在了河的對岸,(1,2)這種狀態只能轉換到(1)(2)兩種狀態,對應的邊的權值分別為2,1分鐘,(1,2)轉換到(1)時也就是2返回了,返回需要耗時2分鐘,以此類推可以建立圖論模型。
要求出最少需要多長時間4人全部通過小橋實際上就是在圖中求出(空)節點到(1,2,5,10)節點間的最短路徑。最後可用Dijkstra演算法求出最短路徑。
Dijkstra演算法是典型的最短路徑演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充,直到擴充到終點為止。Dijkstra演算法能得出最短路徑的最優解,但由於它遍曆計算的節點很多,所以效率低。
Dijkstra演算法是很有代表性的最短路演算法,在很多專業課程中都作為基本內容有詳細的介紹,如資料結構,圖論,運籌學等。
Dijkstra演算法思想為:設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑,就將加入到集合S中,直到全部頂點都加入到S中,演算法就結束了),第二組為其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。
(1)初始時,S只包含源點,即S=,v的距離為0。U包含除v外的其他頂點,U中頂點u距離為邊上的權(若v與u有邊)或(若u不是v的出邊鄰接點)。
(2)從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。
(3)以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u(u U)的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。
(4)重複步驟(2)和(3),直到所有頂點都包含在S中。
Dijkstra演算法是由荷蘭電腦科學家艾茲格·迪科斯徹發現的。演算法解決的是有向圖中最短路徑問題。
舉例來說,如果圖中的頂點表示城市,而邊上的權重表示著城市間開車行經的距離。 Dijkstra演算法可以用來找到兩個城市之間的最短路徑。
Dijkstra演算法的輸入包含了一個有權重的有向圖G,以及G中的一個來源頂點S。 我們以V表示G中所有頂點的集合。圖中的每一個邊,都是兩個頂點所形成的有序元素對。(u,v)表示從頂點u到v有路徑相連。 假設E為所有邊的集合,而邊的權重則由權重函數w: E → [0, ∞]定義。 因此,w(u,v)就是從頂點u到頂點v的非負花費值(cost)。
邊的花費可以想像成兩個頂點之間的距離。任兩點間路徑的花費值,就是該路徑上所有邊的花費值總和。 已知有V中有頂點s及t,Dijkstra演算法可以找到s到t的最低花費路徑(i.e. 最短路徑)。 這個演算法也可以在一個圖中,找到從一個頂點s到任何其他頂點的最短路徑。
演算法描述
這個演算法是通過為每個頂點v保留目前為止所找到的從s到v的最短路徑來工作的。初始時,源點s的路徑長度值被賦為0(d[s]=0), 同時把所有其他頂點的路徑長度設為無窮大,即表示我們不知道任何通向這些頂點的路徑(對於V中所有頂點v除s外d[v]= ∞)。當演算法結束時,d[v]中儲存的便是從s到v的最短路徑,或者是無窮大(如果路徑不存在的話)。
Dijstra演算法的基礎操作是邊的拓展:如果存在一條從u到v的邊,那麼從s到v的最短路徑可以通過將邊(u,v)添加到s到u的尾部來拓展。這條路徑的長度是d[u]+w(u,v)。如果這個值比目前已知的d[v]的值要小,我們可以用新值來替代當前d[v]中的值。拓展邊的操作一直執行到所有的d[v]都代表從s到v最短路徑的花費。這個演算法經過適當的組織因而當d[u]達到它最終的值的時候,每條邊(u,v)都只被拓展一次。
演算法維護兩個頂點集S和Q。集合S保留了我們已知的所有d[v]的值已經是最短路徑的值頂點,而集合Q則保留其他所有頂點。集合S初始狀態為空白,而 後每一步都有一個頂點從Q移動到S。這個被選擇的頂點是Q中擁有最小的d[u]值的頂點。當一個頂點u從Q中轉移到了S中,演算法對每條外接邊(u,v)進 行拓展。
演算法思想
設S為最短距離已確定的頂點集(看作紅點集),V-S是最短距離尚未確定的頂點集(看作藍點集)。
①初始化
初始化時,只有源點s的最短距離是已知的(SD(s)=0),故紅點集S={s},藍點集為空白。
②重複以下工作,按路徑長度遞增次序產生各頂點最短路徑
在當前藍點集中選擇一個最短距離最小的藍點來擴充紅點集,以保證按路徑權重遞增的次序來產生各頂點的最短路徑。
當藍點集中僅剩下最短距離為∞的藍點,或者所有藍點已擴充到紅點集時,s到所有頂點的最短路徑就求出來了。
注意:
①若從源點到藍點的路徑不存在,則可假設該藍點的最短路徑是一條長度為無窮大的虛擬路徑。
②從源點s到終點v的最短路徑簡稱為v的最短路徑;s到v的最短路徑長度簡稱為v的最短距離,並記為SD(v)。
(3)在藍點集中選擇一個最短距離最小的藍點k來擴充紅點集
根據按長度遞增序產生最短路徑的思想,當前最短距離最小的藍點k的最短路徑是:
源點,紅點1,紅點2,…,紅點n,藍點k
距離為:源點到紅點n最短距離+<紅點n,藍點k>邊長
為求解方便,設定一個向量D[0..n-1],對於每個藍點v∈ V-S,用D[v]記錄從源點s到達v且除v外中間不經過任何藍點(若有中間點,則必為紅點)的"最短"路徑長度(簡稱估計距離)。
若k是藍點集中估計距離最小的頂點,則k的估計距離就是最短距離,即若D[k]=min{D[i] i∈V-S},則D[k]=SD(k)。
初始時,每個藍點v的D[c]值應為權w<s,v>,且從s到v的路徑上沒有中間點,因為該路徑僅含一條邊<s,v>。
注意:
在藍點集中選擇一個最短距離最小的藍點k來擴充紅點集是Dijkstra演算法的關鍵
(4)k擴充紅點集s後,藍點集估計距離的修改
將k擴充到紅點後,剩餘藍點集的估計距離可能由於增加了新紅點k而減小,此時必須調整相應藍點的估計距離。
對於任意的藍點j,若k由藍變紅後使D[j]變小,則必定是由於存在一條從s到j且包含新紅點k的更短路徑:P=<s,…,k,j>。且D [j]減小的新路徑P只可能是由於路徑<s,…,k>和邊<k,j>組成。
所以,當length(P)=D[k]+w<k,j>小於D[j]時,應該用P的長度來修改D[j]的值。
(5)Dijkstra演算法
Dijkstra(G,D,s){ //用Dijkstra演算法求有向網G的源點s到各頂點的最短路徑長度 //以下是初始化操作 S={s};D[s]=0; //設定初始的紅點集及最短距離 for( all i∈ V-S )do //對藍點集中每個頂點i D[i]=G[s][i]; //設定i初始的估計距離為w<s,i> //以下是擴充紅點集 for(i=0;i<n-1;i++)do{//最多擴充n-1個藍點到紅點集 D[k]=min{D[i]:all i V-S}; //在當前藍點集中選估計距離最小的頂點k if(D[k]等於∞) return; //藍點集中所有藍點的估計距離均為∞時, //表示這些頂點的最短路徑不存在。 S=S∪{k}; //將藍點k塗紅後擴充到紅點集 for( all j∈V-S )do //調整剩餘藍點的估計距離 if(D[j]>D[k]+G[k][j]) //新紅點k使原D[j]值變小時,用新路徑的長度修改D[j], //使j離s更近。 D[j]=D[k]+G[k][j]; } }