以下文章來源於數據魔術師 ,作者周航
欲下載本文相關的代碼及算例,請關注公眾號【程序猿聲】,后台回復【TSVRPJAVA】不包括【】即可

前言
大家好呀!
眼看這9102年都快要過去了,小編也是越來越感覺着急了:
為什么感覺自己今年還這么蔡!

所以趕緊趁考試周來臨前,碼出了這篇禁忌搜索算法解決VRPTW的文章,臨時抱佛腳,假裝自己今年學了一點東西。
本文附帶Java代碼詳解,是根據過去學長寫的C++代碼修改而來的:
干貨 | 十分鍾掌握禁忌搜索算法求解帶時間窗的車輛路徑問題(附C++代碼和詳細代碼注釋)
新的代碼加入了原先忘加的藐視准則,將一些冗余代碼改為函數調用,同時加入大規模注釋,很適合沒嘗試過VRPTW的同學學習(沒錯就是我自己)。
算例是用上文的格式,所以建議在仔細閱讀本文代碼之前先瀏覽一下上文。
下面就開始今天的分享吧!
VRPTW簡介
VRPTW問題可描述為:假設一個配送中心為周圍若干個位於不同地理位置、且對貨物送達時間有不相同要求的客戶點提供配送服務。其中,配送中心用於運行的車輛都是同一型號的(即擁有相同的容量、速度);配送中心對車輛出入的時間有限制。我們的任務是找出使所有車輛行使路徑總和最小的路線。
VRPTW的更多詳細介紹可以參考之前的推文:
干貨|十分鍾快速掌握CPLEX求解VRPTW數學模型(附JAVA代碼及CPLEX安裝流程)
為了保持文章的獨立型,同時方便后續講解,這里給出建模實例(參考文獻在文末標注):
所求的所有車輛路線需滿足以下要求:

在此基礎上求出每輛車輛的總時間最短(由於車輛速度相同,時間最短相當於路程最短)的路線。(允許不使用某些車輛)
Tabu Search簡介
禁忌搜索算法(Tabu Search Algorithm,簡稱TS)起源於對於人類記憶功能的模仿,是一種亞啟發式算法(meta-heuristics)。它從一個初始可行解(initial feasible solution)出發,試探一系列的特定搜索方向(移動),選擇讓特定的目標函數值提升最多的移動。為了避免陷入局部最優解,禁忌搜索對已經歷過的搜索過程信息進行記錄,從而指導下一步的搜索方向。
禁忌搜索是人工智能的一種體現,是局部搜索的一種擴展。禁忌搜索是在鄰域搜索(local search)的基礎上,通過設置禁忌表(tabu list)來禁忌一些曾經執行過的操作,並利用藐視准則來解禁一些優秀的解。
有關禁忌搜索算法的具體內容可以參考往期推文:
TS求解VRPTW

對鄰域搜索類算法而言,采取的搜索算子和評價函數至關重要。下面詳細介紹代碼中針對VRPTW的插入算子和評價函數。
插入算子:

評價函數:


算法概述


Java代碼詳解
欲下載本文相關的代碼及算例,請關注公眾號【程序猿聲】,后台回復【TSVRPJAVA】不包括【】即可

代碼主要分為以下幾個類:
Main,主函數;
CustomerType,存放客戶節點的信息;
RouteType,存放車輛路線信息;
Parameter,存放全局變量;
EvaluateRoute,處理路線方法;
InitAndPrint,初始化與輸出對應方法;
TS,禁忌搜索方法。
接下來分別介紹。
Main:程序的入口。
CustomerType:客戶類,對圖中的每一個客戶,分別構建客戶類,存放自身編號,所屬車輛路線,坐標位置,訪問時間窗,服務所需時長、需求。
RouteType:路線類,記錄該路線的總承載量,總長度,對時間窗約束的總違反量,以及單條路徑上的客戶節點序列。
Parameter:參數類,有關VRPTW和TS的變量都存儲在這里,在這里修改數據。
EvaluateRoute:check函數是對產生解的檢驗。
由於插入算子產生的解並不都滿足所有約束條件,對局部搜索產生的較優解需要判斷是否滿足時間窗約束和容量約束后,再決定是否為可行解。
在check局部最優解的過程中,修改懲罰系數Alpha、Beta的值。
UpdateSubT函數更新一條車輛路線中在每一個客戶點的時間窗違反量。通過遍歷整條路線累加得到結果。
Calculate函數計算目標函數值,懲罰部分累加后乘以懲罰系數。
InitAndPrint:根據計算距離。
從文件中讀取算例(在此修改算例,記得同時修改Parameter類中的參數),並對當前解routes[ ]的每條路線進行初始化,起終點都為配送中心。
記錄客戶間距離,存儲在Graph數組中。
Construction構造初始解。根據前文偽代碼構造初始解,每次隨機選擇節點(類似打亂有序數列)。
針對該節點找到符合容量約束,同時時間窗開啟時間符合要求的位置,插入該節點。記得在插入節點時同時更新該節點所屬的路徑。對時間窗違反量進行初始化。
最后加入一個CheckAns函數,檢驗一下輸出解是否滿足時間窗約束,計算的距離是否正確,有備無患~
TS:先是兩個輔助函數,addnode和removenode,他們是插入算子的執行部分。
現在萬事俱備,只欠東風,只需要按照禁忌搜索的套路,將所有工具整合在一起,搭建出代碼框架就ok啦。
由於我們采用routes[ ]數組存儲當前解,因此在進行插入操作之前要存儲部分數據,在計算完目標函數之后要進行復原操作。
在更新禁忌表時,對禁忌步長的計算公式可以靈活改變。
記得對局部最優解進行判斷,再選取為可行的全局最優解。
算例展示
我們采用標准solomon測試數據c101.txt進行測試。(算例可在留言區下載獲取)
VehicleNumber = 25;
Capacity=200;
分別測試節點數25,50,100的情況。精確解分別為:

CustomerNumber=25:

CustomerNumber=50:

CustomerNumber=100:

可見我們的代碼精確度還是很可以滴~~
當然不排除運氣不好,得出很差解的情況。不相信自己的人品可以手動調整迭代次數IterMax。
本期的內容到這里就差不多結束了!開心!
在這里提醒大家一下,在針對啟發式算法的學習過程中,編寫代碼的能力是很重要的。VRPTW是一個很好的載體,建議有時間的讀者盡量將學到的算法知識運用到實踐中去。小編會和你們一起學習進步的!
下次再見ヾ( ̄▽ ̄)ByeBye
參考文獻:
Cordeau, J. F. , Laporte, G. , & Mercier, A. . (2001). A unified tabu search heuristic for vehicle routing problems with time windows. Journal of the Operational Research Society**, 52(8), 928-936.
代碼參考:
