1. 程式人生 > >《演算法導論》筆記(16) 單源最短路徑 部分習題

《演算法導論》筆記(16) 單源最短路徑 部分習題

習題21.1-3 Bellman-Ford演算法改進為m+1次鬆弛後終止。圖中結點若在s->v的路徑中則作標記。鬆弛過程中,若有標記的結點全部不更新v值,則停止。此時鬆弛次數為m+1趟。

習題21.1-5 鬆弛方法改為結點已有d值,對其所有入邊選擇w+ d< d 中最小的代替原有的d,按照Bellman-Ford演算法執行V-1趟。

習題21.1-6 尋找權重為負值的環。用2維矩陣儲存所有結點之間的最短路徑,也包括自己到自己的路徑。然後按照Bellman-Ford演算法執行V-1趟鬆弛。L'(i, j)=min{L(i, k)+ w(k, j) },其中L'(i, j)是執行過程中,最新的i結點到j結點的最短路徑。L(i, k)是上次執行得到的i結點到任一結點k的最短路徑。L(i, i)是自己到自己的最短路徑,L(i, i)為負值的結點,即是負值的環路上的結點。

習題24.2-4 計算有向無環圖中的路徑總數。拓撲排序後,從起點到終點,遍歷每個結點同時計算路徑數。P(v)=∑P(u),即結點的路徑總數等於所有入邊的前驅的路徑數之和。

習題24.3-6 最可靠的通訊鏈路。與原始的路徑權重相加不同,通訊鏈路上不失效的概率應該相乘。則可以將概率取10為底的對數,作為路徑權重。然後用一般的單源最短路徑演算法就可以解決了。

習題24.3-8 權重函式w: {0, 1, 2,..., W},修改Dijkstra演算法計算源點到所有結點最短路徑,時間為O(W* V+ E)。因為路徑權重都是0到W的自然數,所以優先佇列可以用計數排序實現。則執行時間為O(W* V+ E)。其中,V*W是結點總數V乘每次從優先佇列中取出時的代價W。E是鬆弛的總次數。

習題24.3-9 上題繼續修改,滿足執行時間O((V+E)lgW)。最小優先佇列的實現方式改為一棵平衡二叉搜尋樹,每個結點代表0~W之間的一個自然數,插入時每放入一個元素,則結點計數加1,向上將父結點加1,直到根結點。查詢時若左子結點的計數>0則向左下移動,否則返回本結點。如此則插入與查詢的時間均為O(lgW)。Dijkstra演算法需要將遍歷V個結點,每次在二叉樹中查詢一次,並且對E條邊每條都有一次插入二叉樹操作。則總時間是O((V+ E)* lgW)。

習題24.4-4 單源最短路徑問題表示為線性規劃問題。對於任意邊E(u, v),w(u, v)是從u到v的邊的權重,則有方程xv- xu<= w(u, v)。現尋找s到t的最短路徑,即xt-xs的最小值。Bellman-Ford演算法可以解。

習題24.4-5 修改Bellman-Ford演算法,使其能夠在O(nm)時間內解決由n個未知變數和m個約束條件構成的差分約束系統。一個可行的方案是,將每個結點賦初值0,從而省略掉初始源點。因為初始源點只有出邊,沒有入邊,所以迭代過程不會影響初始源點以及初始源點到任一點的路徑權值。這樣可以將邊的數量降為m,結點數量降為n,總時間為O(n*m)。

習題24.4-9 Ax<= b為n個變數與m個約束條件的差分約束系統。證明Bellman-Ford演算法可以得到max{x} - min{x}的最小值。首先,收斂過程保證了任意結點的權重是滿足約束方程的一個解,那麼在從初始源點到任意結點的一條路徑上,兩點i、j之間的權重差值,就是兩個解的差值xi- xj 的最小值。遍歷所有路徑,可以找到在一條路徑上的兩個結點,他們之間的權重之差是max{x}- min{x}的最小值。

習題24.4-12 與24.4-11基本相似。Ax<= b的差分約束系統,b所有元素為實數,變數中的某給定子集為整數。先用Bellman-Ford演算法得到一個實數的可行解。然後按照得到的路徑反向遍歷,從初始結點開始,每經過一條邊,就將後一個結點的權重賦值為前一結點的權重加上路徑權重。對於整數的結點,將結點權重取整,然後繼續向後遍歷。

G(u, v)
Bellman-Ford(){
  for i=1 to V-1
    for each edge(u, v) in G
      relax(u, v, w);  if(relaxed) u.p=v; //找到屬於最短路徑中的路徑
}
DFS_find_path(){
  for each vertex v{
    add v to v.p.child[]; //路徑反向,構成源點到任意點的路徑的森林
    mark v as unvisited;
  }
  for each vertex v in source_s.child[] {
    mark v as visiting;
    insert all v.child[] in stack;
  }
  while(u=stack.pop()){ 
    insert all u.child[] in stack;
    mark u as visiting;
    u.d= u.p.d+ w(u, v); 
    if(u belong in integer_set) u=u]; //向下取整
    if(u.child[] all mark as visted) mark u as visited;
  }
}

習題24.5-8 G(V, E)為有向圖,不包含權重為負值的環路。證明所有結點v,存在|V|-1個鬆弛步驟組成的鬆弛序列來生成v.d=δ(s, v)。用數學歸納法。首先證明若最短路徑只有1條邊情況,Initialize_Single_Source(G, s)就可以收斂。假設最短路徑有n條邊,經過n-1次鬆弛可以收斂。那麼在最短路徑為n+1條邊的情況下,n-1次鬆弛得到v的前驅u的最短路徑權重u.d,然後多1次鬆弛可以得到v.d。可見n+1條邊的最短路徑需鬆弛n次。全部結點最多需鬆弛|V|-1次。

思考題24-1 Yen對Bellman-Ford演算法的改進。Gf中的任意邊(i, j)都滿足i< j的偏序關係,那麼DFS深度遍歷時,i.d>j.d,並且j的後繼不可能是i,則Gf中無環,且拓撲排序時i一定在j的前面。Gb同理。

一遍鬆弛的過程,Gf與Gb都可以得到各自的最短生成路徑。G上一條完整的最短路徑,最多可以包含|V|-1條邊,其中分屬於Gf與Gb。可以知道,此完整路徑只需要max{edges in Gf, edges in Gb }次鬆弛。則最多需要|V|/2次鬆弛可以得到完整的最短路徑。

漸近時間要考慮排序的時間。首先是給所有結點一個隨機數以確定偏序,時間O(|V|),然後給所有邊標記Gf或Gb,時間O(|E|),最後是|V|/2趟鬆弛,總時間O(|V|+|E|+|V|*|E|/2),比原版Bellman-Ford演算法節省了常數時間。

思考題24-2 巢狀盒子問題。可以先對兩個盒子的元素排序,然後比較各相同位置的元素,對任意i<= A.size,A[i]< B[i],則A可以巢狀到B內。

最長巢狀盒子序列。所有盒子排序,然後給出一個拓撲排序,順序基於兩個盒子能否巢狀,有巢狀關係的盒子作為一條有向邊。這樣就轉變成了尋找一條最長路徑的問題。從起點到終點一趟鬆弛可以完成。

思考題24-3 套利交易問題。任意兩種貨幣之間的匯率表示為雙向的邊,權重為匯率r[i, j]和r[j, i]= 1/r[i, j]。尋找一個環路,是否經過所有路徑的權重乘積∏r >1。所有r轉換為log對數座標,則相乘轉換為相加。然後Bellman-Ford演算法尋找一個權重為負的環路即可。

思考題24-5 Karp的最小平均權重環路演算法。u= min max(δn(s,v)- δk(s,v))/ (n-k)。對每個結點v,  令δ[k-1](s, u) 是其所有入邊的前驅點u的擁有k-1條邊的最短路徑。則δ[k](s, v)是δ[k-1](s, u) + (u, v)的鬆弛結果。經過V-1輪鬆弛,可以得到k為從1到E的全部結點的全部δ[k],並計算出u,時間為O(VE)。

思考題24-6 雙調最短路徑。因為最短路徑都是雙調最短路徑,所以沿著任意最短路徑的邊都是權重先增大再減小。鬆弛時把所有邊按照權重增大的順序鬆弛一次再按照權重減小的順序鬆弛一次,就可以保證任意最短路徑上的結點都經歷了一次從起點到終點和一次從終點到起點的鬆弛過程。鬆弛次數為2就可以得到所有的最短路徑。