如何實現(xiàn)一個高效的啟發(fā)式算法?
一、前言
小伙伴們好,說起來已經(jīng)好久好久好久沒見了呢!之前一直忙著做其他事情去了(泛指學習一類),公眾號已經(jīng)落下好久好久了。今天來寫點好玩的東西。
說起來,小編似乎就是做啟發(fā)式算法起家的。當時記得老師是這么跟我說的,啟發(fā)式算法這東西很簡單,你不需要基礎(chǔ),有高中基礎(chǔ)就夠了(其實他想說的是初中……)。

后來小編一直在學這個東西,做了三四年了,用啟發(fā)式算法做過的大大小小的project已經(jīng)不記得有多少了,所以還算得上有一點點經(jīng)驗。因此今天就來寫寫,怎樣實現(xiàn)一個比較高效的啟發(fā)式算法吧~
二、何為高效?
說到這個詞,相信大家一定不陌生。高效意思就是達到相同效果或者更好的效果時,使用的時間更短,所需要的資源更少。就拿小編來說,由于小編特別笨,學一樣東西需要花一周的時間,而群里的小伙伴只需要一天的時間就能學會。那么這位小伙伴是要比我高效的。

同樣的對于一個啟發(fā)式算法而言,不同人實現(xiàn)出來,即使是使用同一編程平臺達到同樣的效果,運行時間也會千差萬別,相差幾倍甚至幾十倍。這樣說出來大家可能還沒啥感覺,那么放一下我們之前做過的一些數(shù)值實驗大家直觀感受下吧~

這是某個Java實現(xiàn)的求解VRP類問題的算法代碼,兩個算法都達到了同樣的效果,只不過綠色曲線對應(yīng)的算法在計算過程中去除了相關(guān)冗余,可以看到運行時間直線下降。根據(jù)我們的統(tǒng)計,消除冗余計算后可使計算時間降低約83%,對于工業(yè)化生產(chǎn)而言,提升哪怕一個小數(shù)點都能帶來巨大的收益,何止是83個百分點呢。怎么說呢,這簡直是A small step forward,one step civiliazation。

三、放碼過來?
不要一上來就是寫代碼,不加思考就上手寫代碼,你只會搓一坨屎山出來,自己坑害自己。開始寫代碼之前一定要構(gòu)思好算法的整體架構(gòu),解的表示方式,如何快速得到鄰居解等。建議是思考的時間一定要占總時間的一半以上。

其實思路清晰寫代碼是非??斓?,比如每次在寫代碼的時候我都會先寫好注釋,比如:
//1.?先獲取所有可行點的信息
//2.?對點按照成本進行排序
//3.?貪心將各個點插入到解中
然后寫的時候我只需要按照這個思路往下走就可以了,這就跟你寫小學生作文一樣,起床刷牙到公園看鯨魚,一定要思路清晰。
四、鄰居解如何計算?
到了今天的核心問題,我們都知道,鄰域搜索過程中,鄰居解與當前解相比往往只有細微的變化,因此迭代過程中絕大部分變量不需要重新計算,消除了冗余計算,可大大提高鄰域搜索效率,降低運行時間。聽不懂嗎?沒關(guān)系,我舉例子,慢慢給你講解。
下面是一個VRP問題(沒有TW哦)的一個初始解:

為了方便表示我們用表示x的cost吧,其中x可以為一個解、解中的一條路徑。表示邊的距離。對于初始解,計算它的cost,只能是從頭到尾挨個遍歷一下了。因此:
其中各條路徑的cost又可以表示為:
因此最后解的計算方式為:
為了方便比較我們將這種計算解cost的方式稱為
Algorithm1。
算一下,Algorithm1在計算時遍歷了所有的客戶點,對于N個客戶點的解而已,算法的復雜度為O(N),嗯!還不賴。
好了現(xiàn)在解通過一個move,生成了一個鄰居解:

可以看到該move就是將客戶19從路徑中移除,并重新relocate到路徑中客戶1后面。
現(xiàn)在生成了一個鄰居解,得知道這個鄰居解是好還是壞對吧,那么我們得比較和的大小吧。前面我們通過Algorithm1算出了的大小,那么現(xiàn)在的問題就是怎么計算了。敲黑板的重點來了。
利用Algorithm1對重新進行計算,時間復雜度前面分析過了,為。
優(yōu)化一下
觀察上面的解和,可以發(fā)現(xiàn)一個鄰域搜索算法非常顯著的特點:鄰居解相比較當前解而言,只發(fā)生了微小的改變,整個解中有4條路徑,只有兩條路徑發(fā)生了改變,因此的cost可以由原來計算好的一些結(jié)果進行換算:
在上面的式子中,只有和是需要重新算的:
這樣一來,只需要計算變動的路徑即可,就不用重新計算所有路徑了。大大降低了鄰居解的計算時間復雜度。
進一步優(yōu)化
細細觀察一下和我們發(fā)現(xiàn),路徑中發(fā)生變動的邊似乎也不是很多啊。我給大家標一下:
中發(fā)生變動的邊我已經(jīng)用紅色實線標出來了,中發(fā)生變動的邊我用紅色虛線標出來,那么和是不是可以用之前計算好的和得出來呢?當然可以啦,我們只需減去原來的邊,再加上新的邊就可以了。
我們將這兩條式子帶入下面的式子:
即可得到:
這個時間復雜度為,OK。經(jīng)過重重優(yōu)化,將時間復雜度從原來的成功降到了。
可能大家對這個降到沒什么感覺。我來給大家分析一下,在小規(guī)模問題,比如10個、20個節(jié)點這樣可能還真沒啥區(qū)別。但是別忘記了啟發(fā)式算法是針對大規(guī)模的優(yōu)化問題的,鄰域搜索類算法的鄰域規(guī)模往往是隨著問題規(guī)模的增長而呈爆炸式增長的。比如說在一個100個節(jié)點的VRP算例中,對于exchange算子,交換任意兩個客戶,那么一個解所能形成的鄰居解就有,大約為5000個鄰居解。
如果每個鄰居解你都使用 Algorithm1重新算一遍,那么大概要算5000*100=500000次。如果你用優(yōu)化過的計算方法,只需要算5000*1=5000次。
這個差距有多大,大家動動屁股都知道了。當然了,這只是理論上的分析。實際上的差距和編程環(huán)境以及實現(xiàn)方式等都有很大關(guān)系,但是只要差距超過10倍以上,都是能很明顯的感覺出來的。
五、小結(jié)
關(guān)于如果去除冗余,不是說一套方法通用的,需要根據(jù)算法工程師根據(jù)問題的特性,設(shè)計合適的解結(jié)構(gòu),再對鄰域算子進行降冗余的思考與實現(xiàn)。降冗余的操作比較適合鄰域搜索類的啟發(fā)式算法,因為這類算法顯著特點就是鄰居解相比較當前解而言,變化非常細微。
當然了,這需要豐富的編程經(jīng)驗,所以大家有時間還是好好磨練下吧~當然了這次也會放出相應(yīng)的學習代碼,只需要在公眾號回復【VRP去重】即可看到。
還有,寫到這里,我突然想起大一發(fā)生的一件糗事,那時候大家統(tǒng)一穿著綠色的軍裝。我去上完廁所以后突然不知道自己屬于哪個隊伍了。于是當時被教官狠狠訓了一頓。后來才知道,是三連。

