疫情原因,公司乾脆利落地把我們業務組給裁啦,我也光榮地成爲了一個下崗待業的老程序員。
開發工作不好找啊,畢竟都要35歲以下的,所以我尋思再就業可以換個方向,比如說送外賣,再怎麼說X團、X了麼也是大廠嘛~
既然下定決心,第一步就是要武裝頭腦,拿起理論的武器,送外賣第一要務是什麼?快!!天下武功,唯快不破。速度速度速度,重要的事情說三遍。
如何快速抵達商家,再快速將飯菜送到顧客手中,少跑路是關鍵——這就是最短路徑問題,下面我就描述一下,我是如何使用Dijkstra算法結合回龍觀的地圖來計算最短路徑的。
選回龍觀的原因:1、回龍觀的道路情況很好,基本上是橫平豎直;2、我家就在這,送外賣在家附近送,路熟。
回龍觀的地圖如上,因爲是做實驗,就選取了部分區域:北起回南北路,南至同成街;東起G6輔路,西至文化東路。
之前我的兩篇描述最短路徑算法的文章中,使用的圖都是類似:
如果用在地圖上,就有些不合適,因爲不太好體現真實情況,如下圖:
在示意圖中,兩點之間只會有一條邊A,但現實中,兩地的路線有B、C兩條,所以爲了讓示意圖更貼合顯示,就有了變種圖:
每一個方塊代表一個頂點,兩個相鄰頂點的邊權重視爲1.
如上圖,其中藍色數字的方塊代表可通過的路,紅色英文的方塊代表不可通行,其頂點邊權組合應如下:
1-2-1,1-16-1
2-1-1,2-3-1
3-2-1,3-4-1,3-17-1
4-3-1,4-5-1
5-4-1,5-6-1
...... ......
18-17-1,18-19-1,18-20-1,18-21-1
假如以北店嘉園北區東門爲起點,即19,以龍回苑西門爲終點,即5.
可得最短路線爲19-18-21-7-6-5或19-18-17-3-4-5,再結合實際道路情況,例如擁堵程度、紅綠燈、單行路等,可得出相對較短的路線。
假設龍禧二街常年堵車,邊權設爲2,則較短路徑應爲第二條。
我使用地圖的測距工具得到了 以下不怎麼精確的距離:
以路口爲點,兩點之間的距離如上圖,單位公里。
我假設每一個方塊都代表100*100米,那麼示意圖如下:
不要問爲什麼是圓而不是說好的方塊,因爲圓比方塊好畫~~~
也請忽視圓的大小不一,單位固定都是100*100米。
接着我們上代碼。
首先我們必須構建頂點的邊權數據,類似1-45-1,1-74-1這種。
在這裏我採用了Guava裏的HashBasedTable結構,即Key1-Key2:Value。
Table<Integer, Integer, Integer> ppw = HashBasedTable.create();
ppw.put(1, 45, 1);
ppw.put(1, 74, 1);
ppw.put(45, 1, 1);
ppw.put(74, 1, 1);
在本圖裏,共有296個頂點,相關邊更多,純手工錄入會崩潰的,所以我寫了程序,根據輸入的頂點範圍生成相應的代碼。即便這樣也是很累人的。
接着實現Dijkstra算法。僞代碼如下,入參有兩個:起點,終點,
public void getShortestPath(Integer start, Integer end){
1、構建到某一頂點最短路徑的起點Map——parentMap
2、構建已處理最短路徑頂點Map——s;構建待處理最短路徑頂點Map——w
3、構建(頂點A-頂點B:邊權)的Table——ppw
4、遍歷所有頂點{
4.1將w轉爲優先隊列,並取出最小值的頂點,將其從w挪入s,並以此爲頂點(設爲Key1)計算其相鄰頂點的權重。
4.2如果取出的最小值頂點就是終點,且最小值不是無窮大(在程序中用Integer.MAX_VALUE代替),說明已經計算到終點,不需要計算後面的點,直接跳出循環。
4.3內循環遍歷所有頂點(設爲Key2),根據Key1-Key2從ppw中取出權重。
4.4如果ppw取出爲null,說明兩頂點間無路徑。
4.5根據Key1、Key2的值、邊權對Key2進行鬆弛操作。
}
}
至此,核心代碼已經完成,後續還有輸入騎手、商家、顧客,調用核心代碼,並輸出路徑的方法等,在此不再贅述。
以下爲測試結果。
假設騎手在回龍觀西大街與育知東路的交叉口(即24),商家在北京華聯同成街店(即177),顧客在天露園二區北門(即89),計算的結果如下:
以商家到顧客路線爲例,地圖給出的路徑如圖:
程序給出的路徑:
基本還算一致。
當然這只是一個很簡陋的程序,有許多實際問題沒有考慮,比如出行方式、擁堵、紅綠燈、單行道、禁行路段、立體交通等。
例如點26,同成街與育知東路交叉點,這其實是個橋,如果從175到236,開車的話是不可能走175-26-236路線的,必須繞一圈。
這篇文章其實也只是記錄一下個人將理論與實際相結合的學習過程,疏漏錯誤在所難免。
好了,不說了,僅有理論的指導還是不夠的,我去升級裝備了。