代碼黑科技的分享區java
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1、前言
很早以前就想寫這篇文章了,因爲各類不可描述的緣由拖延到了如今,今兒就把坑給填上吧~node
前排提示:小夥伴們能夠收藏下這篇文章哦,說不定那天大家就用上了。由於真的是很乾貨哦!git
2、Open TS算法框架
作元啓發式的小夥伴都知道,一開始須要學習一些固定的算法框架,這是理論基礎。有了理論基礎之後就能夠針對各類奇奇怪怪問題把這些算法框架給套上去,總能跑出一些結果,不管是好的差的。github
常常有不少人來問我,這個問題用什麼算法比較好啊?那個問題用什麼框架比較合適?一開始我還很耐心的跟他們扯淡說:沒有最好的,只有更好的。。。其實不是的,按照我這幾年作啓發式的經驗來講,算法框架這東西其實吧,只要是一個類別的,基本都不會有太大差異(好比TS和SA、LNS和ALNS、GA和AFAS等等)。咱們作算法開發的時候,更應該把重心放在一些問題特性的推導,或者搜索算子的思考上。web
好了又扯遠了……今天的主題是分享一份代碼,一個開源的Tabu Search框架,以及如何利用該框架進行二次開發。先介紹下今天的主角:Open TS。這個是由Robert Harder開發的一個基於java平臺tabu search算法框架。用他的話說就是:算法
Use these classes to help build a structured and efficient Java tabu search.編程
![](http://static.javashuo.com/static/loading.gif)
該package包含了實現一個tabu search框架須要的各類元素,能夠說得上很是全面了。你們在編寫tabu search相關的算法時,只須要extend相關的class或者implements相關的interface便可。微信
![](http://static.javashuo.com/static/loading.gif)
這就使得咱們能夠將更多的時間和精力放在算子的設計以及其餘問題特性的考慮上,而不是將大量的時間浪費在維護算法框架上。固然了,這個框架因爲考慮了不少general的東西,同時也作了不少額外的異常處理什麼的從而使得代碼更爲健壯。thus,代碼的速度可能就會有一點點損失。app
嗯……我這裏指的損失是相對那種超級大神級別的人來講的,畢竟他們寫代碼會把各類冗餘的計算去掉,把全部的可能slow down算法速度的因素都杜絕掉,巴不得直接用匯編寫的那種……咱這些普通打工人也還沒到那麼牛逼的地步嘛!框架
總之,這個算法框架仍是很是牛逼的,平時擼擼論文,作作項目直接拿來作二次開發也是一個不錯的選擇啦。
3、二次開發
其實上面給了不少類,可是對於一個單線程的tabu search來講,並不須要用到全部的class,只須要繼承一些基本的元素,而後針對你的問題將他們special化就行啦。下面我介紹下二次開發的要實現的一些東西吧。
1. SolutionAdapter
求解任何問題,首先仍是要定義該問題的solution結構了。只須要extend Open TS的SolutionAdapter類便可,該類中只有一個成員變量爲:
private double[] objectiveValue;
爲該解的目標值向量,而後你就能夠在你本身的solution中定義問題的其餘變量了。好比存儲路徑啊,解的其餘中間變量等等。
2. TabuSearchListener
該類呢爲接口類,裏面有幾個抽象方法須要實現的,分別爲:
public void newBestSolutionFound(TabuSearchEvent event) {}
找到一個全局最優解時,要作的事情能夠寫在裏面。
public void newCurrentSolutionFound(TabuSearchEvent event) {}
找到一個新的當前解時要作的事情能夠寫在這個函數。
public void tabuSearchStarted(TabuSearchEvent event) {
算法開始時觸發的事件。
public void tabuSearchStopped(TabuSearchEvent event) {}
算法結束時觸發的事件。
基本上從新寫一下上面幾個抽象方法就能夠知足大部分的需求了。固然裏面也定義了一些nonimprove相關的時間,能夠做爲shake使用。
3. ObjectiveFunction
該interface比較重要,繼承之後須要實現下面這個抽象方法:
public double[] evaluate(Solution solution, Move proposedMove) {}
它表示評估當前解solution通過proposedMove之後造成的鄰居解的目標值向量,就是前面SolutionAdapter中那個objectiveValue哦。這是什麼意思呢,其實在算法實現中,咱們通常不直接生成鄰居解,而是生成一個稱之爲 的東西,這是個什麼東西呢,畫個圖:
![](http://static.javashuo.com/static/loading.gif)
其中我用紫色標出來的就是一個 ,簡單來講他記錄了生成鄰居解須要對當前解進行的一些操做,好比exchange(6, 15)。
所以每次生成鄰域時,能夠先生成鄰居解對應的 ,而後去評估每一個 對應的鄰居解的cost,以比較各個鄰居解的好壞。
4. ComplexMove
爲interface,該算法框架的鄰居解是經過當前解+move得到的,所以你的問題中設計的operator算子須要實現該接口,它有一些抽象方法以下:
public abstract void operateOn( Solution soln );
該方法實際上是更上一層extends來的,表示對該move對soln執行相應的操做,好比exchange(1, 2)或者relocate(1, 3)等等。
public abstract int[] attributesDelete();
執行該move時,刪除一個元素時返回的信息提供給tabu list記錄。好比{1, 3}表示exchange了1和3,那麼tabu list就要記錄起來,防止後面的迭代中再進行exchange(1, 3)這樣的操做。
public abstract int[] attributesInsert();
執行該move時,插入一個元素時返回的信息提供給tabu list記錄。和刪除時相似的。
5. MoveManager
這也是一個interface,是否是很爽,只須要implements相關的接口便可完成一個算法,簡直不要過輕鬆!它的抽象方法只有一個:
public Move[] getAllMoves( Solution solution ) { }
這個方法是須要咱們本身實現的,並且很是重要,由於這裏定義了咱們設計的算子所生成的move集合。
我以爲這個框架最好的地方就是這裏了,他把全部的move都放在一塊兒集中進行管理,後面進行約束變動的時候只須要修改這裏的生成規則便可。好比客戶i不能插入路徑j,那麼你在這裏生成的時候就進行這些限制便可。
6. ComplexTabuList
這是一個類,表示tabu search中的禁忌表,裏面有一個多維的tabu list能夠記錄不少信息:
private int[][][][][] tabuList; // Data structure used to store list
同時該類已經實現了setTabu和isTabu的方法。這兩個方法須要結合以前設置的attributesInsert()和attributesDelete()方法一塊兒使用,若是作出修改那麼須要修改相應的這幾部分,特別是tabu list要進行修改的話。。。
4、實例
好了以上就是一些簡單的介紹,固然這樣介紹可能你們沒什麼感受,由於這東西在沒有對代碼有一個很好的全局掌控以前,很難體會到其中的精髓,反而不少人由於其中巨大的代碼量感受極爲繁瑣。
畢竟用別人的東西,萬一出錯了都不知道怎麼調。這裏呢爲了讓你們更好的熟悉這個框架,我貼上了一個使用該框架實現一個求解VRPTW問題的例子,這個代碼是來源於GitHub(好像是意大利都靈理工大學一些masters的課程大做業吧……)原連接爲oma-vrptw。
https://github.com/oma-vrptw/oma-vrptw
這個代碼自己也有不少值得借鑑參考的地方的,好比它裏面實現了一個relocate(代碼中叫SWPA MOVE,可是我以爲relocate更合適點)算子,在評估一個move的時候就用到了此前咱們講過的以O(n)複雜度計算鄰居解的一些操做:
這個算子的效果還能夠的,在Solomon的標準算例中C系列大部分能跑到最優,速度更是快得飛起。你們閱讀源碼時照着我上面貼出來的思路看便可。算例呢我也整合好了,我對源代碼作了一些修改,使得他可以正常運行(否則待會又有不少人跑來問我代碼咋不能運行呢?),更改算例在如下位置便可更改。
![](http://static.javashuo.com/static/loading.gif)
單線程tabu search的主體呢是在SingleThreadedTabuSearch這個類中,執行一次迭代的邏輯都寫在了protected void performOneIteration()這個方法裏面。
其實要寫的比較高效的話,每一個算子生成的move都應該定製好本身單獨的evaluate函數,示例只寫了一個算子,若是move是由多個算子生成的話,須要判斷下move屬於哪一個算子的,而後進行相應的evaluate,能夠更改ObjectiveFunction的evaluate函數成以下形式:
![](http://static.javashuo.com/static/loading.gif)
固然啦,你也能夠修改框架中的代碼以達到更多個性化的功能,不過我是不太推薦這樣作的,由於別人封裝好的東西,你一整的話,出錯了都不知道去哪裏找。不過熟悉之後能夠嘗試修改一下底層的代碼。我就對那個tabu list進行了修改,由於感受給的那個不是很好用吧~
5、代碼下載
我把修改過的代碼放在了GitHub上,地址爲
https://github.com/dengfaheng/omatest
好了,你們能夠慢慢去看代碼了。。。have a nice day!看在小編這麼勤勞的份上,幫我點個在看唄~萬一你的老闆喜歡看微信的看一看,看到你又在微信上學習代碼,ta確定要高興得不得了呀!你就能夠大膽告訴他:
推薦閱讀:
乾貨 | 學習算法,你須要掌握這些編程基礎(包含JAVA和C++)
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
本文分享自微信公衆號 - 程序猿聲(ProgramDream)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。