年底了,在做各種總結,回顧一下2020年的收獲和不足。這一年開了博客,總結了逆向、滲透和網絡安全方面的技術和知識點,距離自己設定的目標又進了一步;總結的時候突然想到了一個經典的路徑規划問題:一個旅行者從A出發到F,中間有多條路線走,哪條路線是成本最低的了? 這個和人生職業發展是不是類似了?小時候立志成為xxx,為了實現理想,可能要分多步走,怎么規划才能成本最低、效果最優了?PS: 路由器的OSPF路徑規划也用了這種算法!
1、下面是示意圖: 起點是A,終點是F,中間點是BCDE,每連個點之間都是通的,路徑的長度如圖所示,從A到F那條路才是最短的(注意:數字是隨機生成的,並不和看起來的長度成正比)?這就是業界著名的Dijkstra動態規划問題;

解決問題的核心思路:
- 既然要找到全局最短的路徑,那么必須找到每一步的最短路徑,所有步驟加起來才會是全局最短的;
- 從A出發,窮舉連接所有節點的距離,選出最短的節點,下一步從該節點繼續重復這一步,直到到達終點;
上面是理論思路,下面具體的步驟:
(1)從A出發,窮舉和A連接所有節點的距離。如果沒直連,距離用NA或無窮大表示,如下:

從A出發,到E的距離最短,此時鎖定E節點,將其作為跳板繼續尋找下一條最優的路線,具體的方法:E直連了D和F,距離分別是3和8,那么A如果以E為中繼節點,到D和F的距離就變成了7+3=10、7+8=15;如果A不以E為中繼節點,到D和F的距離分別是12和無窮大,很顯然通過E中繼比之前的距離更短,這里就更新路由表如下:

因為已經使用了E作為中繼,就不能再動,這里用黃色標記一下;同時把AD和AF截至目前的最短路徑(分別是AED、AEF)也標記一下;至此,已經找到了第一個最優的中繼節點E:通過該節點中繼能縮短現有其他路線的距離(這里縮短了A->D和A->F的距離)!
(2)跟新后的路由表如上:此時繼續選距離最短的點中繼。相比之下,AED距離10是最短的,所以從這里D開始中繼;和D直連的節點是C和F,如果以D為中繼節點,A到C和F的距離分別是10+5=15、10+6=16,和以前比,AC的距離沒變,AF的距離比之前的15還要多1,所以從D出發,並沒有縮短現有路由表的距離,此時忽略D節點中轉,路由表標時如下:

(3)繼續尋找下一個可能的最優中繼節點:先在只剩B和C了,相比之下AB距離14,那么臨時把B作為中繼節點;B只能到F,那么ABF就是14+5=19,還是比現有的AEF大,所以B節點繼續忽略;

(4)先在只剩C節點了: C能到B和F,ACB=21、ACF=25,和現在的AB、AF比並沒有降低,所以不用更新現有路由表;這一步后所有的中間節點都已經遍歷,從A到各個節點最優的路由也就定了,如下:

A到B、C不變,到D和F從E中轉!
2、和Dijkstra動態規划類似的另一個問題:TSP(traveling salesman problem),一個salesman要遍歷所有城市,但每座城市只能去一次,最后回到起點;城市之間互相都是通的,但來去的成本不同,要求遍歷所有城市的成本最低;抽象出來的問題描述如下:
A、B、C、D四座城市互相連通(雙向聯通),城市之間的travle成本如右邊矩陣所示;這個矩陣是非對稱矩陣,具體到業務上就是來回的成本不是一樣的。比如從A->B是16,但是從B->A是8;又比如C->D是9,但D->C是2;從那個城市出發,這個城市就從行查目的地的城市放在列!

不同於上面的Dijkstra路由規划,這里的TSP是雙向的,最核心的區別在於:TSP要求回到出發點,而Dijkstra不需要;前者要求回路閉環,后者要求單向無環!正是這種差異,導致了算法層面剛好相反:Dijkstra是從起點出發(剛開始不知道從哪跳轉,只能從起點開始選下一個中繼點),每次都找最近的節點作為中繼;TSP是知道“終點”,所以從所有能窮舉的“終點”出發,倒過來找成本最低的邊;
具體的過程如下:
(1)先窮舉(這就是這種算法復雜度高的原因之一:如果)出所有從A出發、最終能回到A的路線,如下:

(2)根據上面的那個遍歷矩陣,從底部的“終點”出發,計算出每個分支的遍歷成本,選擇成本最小的分支替代替另一個分支,比如:
- 最左邊的分支:BCDA和BDCA,這兩個分支只能留一個,就看那個分支的成本最低了;BCDA的成本是13+9+5=27,BDCA的成本是16+2+4=22,比前面的小,所以BCDA廢除,這里只保留BDCA;
- 同理:中間的C分支有CBDA和CDBA,成本分別是7+16+5=28和9+12+8=29,保留CBDA,廢除CDBA;
- 同理:最右邊的分支DBCA和DCBA,成本分別是12+13+4=29和2+7+8=17,保留DCBA;
- B、C、D三個分支中,B分支最短是22,是BDCA;C分支最短是28,是CBDA;D分支最短是17,是DCBA;
- AB=16,加上BDCA的22,最終是38;AC=11,加上CBDA的28,最終是39;AD=6,加上DCBA的17,最終是23;所以ADCBA是最優的路徑,成本是17,完勝!
(3)為了便於編碼實現,還需要進一步抽象整個過程,如下:
- g(i,s) = min{w(i,j)+g(j,{s-j})}, 其中i、j是圖中的點,s是去掉出發點后所有點的集合;w(i,j)表示圖中i、j兩個點之間的權重;s-j表示除去j后剩余點的集合;
(3.1)具體的計算表示:g(A,{B,C,D}) = min{w(A,B)+g(B,{C,D})},其中w(A,B)是已知的,那么問題就轉發成了求g(B,{C,D}了;
g(B,{C,D} = min{g(B,C)+g(C,{D})} 或 = min{w(B,D)+g(D,{C})},問題又轉換成了求g(C,{D})和g(D,{C})了,這兩個等價於w(C,D)和w(D,C);
(3.2)上面分解了g(A,{B,C,D}) = min{w(A,B)+g(B,{C,D})},這只是其中一個分支,還需要用同樣的方法求另外兩個分支:g(A,{B,C,D}) = min{w(A,C)+g(C,{B,D})} 和g(A,{B,C,D}) = min{w(A,D)+g(D,{C,D})}
所有分支求出來后,找到最小的分支就是最終的結果!
這兩個算法非常經典,思路也很成熟了,github上的代碼有一堆,感興趣的小伙伴可以自行去找找嘗試嘗試!
注意:TSP解決的算法還有遺傳算法、模擬退火等,上述算法叫深度優先遍歷(俗稱“使蠻力”),只是眾多算法中的一種;
參考:
1、https://www.bilibili.com/video/BV1gz4y1S7jD?p=31 動態規划
2、https://www.油管.com/watch?v=hh-uFQ-MGfw Traveling Salesman Problem using Dynamic Programming | DAA (需要自備梯子;印度人講的,動態演示整個過程,通俗易懂;除了口音聽着憋屈,其他沒毛病!)
