1. 程式人生 > >禁忌搜尋演算法詳解

禁忌搜尋演算法詳解

引言

對於優化問題相關演算法有如下分類:
這裡寫圖片描述
禁忌搜尋是由區域性搜尋演算法發展而來,爬山法是從通用區域性搜尋演算法改進而來。在介紹禁忌搜尋之前先來熟悉下爬山法和區域性搜尋演算法。

區域性搜尋演算法

演算法的基本思想

在搜尋過程中,始終選擇當前點的鄰居中與離目標最近者的方向搜尋。

演算法過程

1)隨機選擇一個初始的可能解x0 ∈D,xb=x0,P=N(xb);
     //D是問題的定義域, xb用於記錄到目標位置的最優解,P為xb的鄰域。2)如果不滿足結束條件,則: 
    //結束條件為迴圈次數或P為空等3Begin;
(4)選擇P的一個子集P',xn為P’的最優解 ;      
    //P’可根據問題特點,選擇適當大小的子集。可按概率選擇
(5)如果f(xn)<f(xb),則xb=xn,P=N(xb),轉(2);    
    //重新計算P,f(x)為指標函式
(6)否則P=P-P'
,轉(2); (7End; (8)輸出計算結果; (9)結束 ;

爬山法

演算法的基本思想

將搜尋過程比作爬山過程,在沒有任何有關山頂的其他資訊的情況下,沿著高度增加的方向爬。如果相鄰狀態沒有比當前值更高,則演算法停止,認為當前值即為頂峰。

演算法過程

1) 設定初始狀態n=s0為當前狀態;
(2) 如果當前狀態已達標,演算法結束,搜尋成功;
(3)獲取當前狀態n的若干個臨近狀態m,計算這些h(m), nextn=min{h(m)};
(4IF h(n) < h(nextn) 
    THEN  n:=nextn;
    ELSE 取當前狀態為最佳狀態並退出;
(5
GOTO (2)步;

該演算法在單峰的條件下,必能達到山頂。
顯而易見爬山法對於複雜情況的求解會遇到以下問題:
(1)區域性極值
(2)山脊:造成一系列的區域性極值
(3)高原:平坦的區域性極值區域——解決辦法:繼續側向移動
目前有些改進的爬山法,比如隨機爬山法、首選爬山法等等不再細說。

禁忌搜尋演算法

演算法思想

標記已經解得的區域性最優解或求解過程,並在進一步的迭代中避開這些區域性最優解或求解過程。區域性搜尋的缺點在於,太過於對某一區域性區域以及其鄰域的搜尋,導致一葉障目。為了找到全域性最優解,禁忌搜尋就是對於找到的一部分區域性最優解,有意識地避開它,從而或得更多的搜尋區域

演算法過程

1)給定一個禁忌表(Tabu List)H=null,並選定一個初始解X_now.
(2)如果滿足停止規則,則停止計算,輸出結果;否則,在X_now的領域中選出滿足不受禁忌的候選集N(X_now).在N(X_now)中選擇一個評價值最賤的解X_next,X_next:=X_now;更新歷史記錄H, 重複步驟(2).

對搜尋效能有影響的因素

禁忌長度

控制其他變數,單就禁忌長度的選擇而言,禁忌長度越短,機器記憶體佔用越少,解禁範圍更大(搜尋範圍上限越大),但很容易造成搜尋迴圈(實際去搜索的範圍卻很小),過早陷入區域性最優。禁忌長度過長又會導致計算時間過長。

特赦規則

通俗定義:對於在禁忌的物件,如果出現以下情況,不論現在物件的禁忌長度如何,均設為0
(1)基於評價值的規則,若出現一個解的目標值好於前面任何一個最佳候選解,可特赦;
(2)基於最小錯誤的規則,若所有物件都被禁忌,特赦一個評價值最小的解;
(3)基於影響力的規則,可以特赦對目標值影響大的物件。

候選集

候選集的大小,過大增加計算記憶體和計算時間,過小過早陷入區域性最優。候選集的選擇一般由鄰域中的鄰居組成,可以選擇所有鄰居,也可以選擇表現較好的鄰居,還可以隨機選擇幾個鄰居。

評價函式

評價函式分為直接評價函式和間接評價函式。
直接評價函式:上述例子,均直接使用目標值作為評價函式。
間接評價函式:反映目標函式特性的函式(會比目標函式的計算更為簡便,用以減少計算時間等)。

終止規則

禁忌演算法是一個啟發式演算法,我們不可能讓搜尋過程無窮進行,所以一些直觀的終止規則就出現了
(1)確定步數終止,無法保證解的效果,應記錄當前最優解;
(2)頻率控制原則,當某一個解、目標值或元素序列的頻率超過一個給定值時,終止計算;
(3)目標控制原則,如果在一個給定步數內,當前最優值沒有變化,可終止計算。

程式碼

public class TabuSearchAlgorithm {
    /** 迭代次數 */
    private int MAX_GEN;
    /** 每次搜尋鄰居個數 */
    private int neighbourhoodNum;
    /** 禁忌長度 */
    private int tabuTableLength;
    /** 節點數量,編碼長度 */
    private int nodeNum;
    /** 節點間距離矩陣 */
    private int[][] nodeDistance;
    /** 當前路線 */
    private int[] route;
    /** 最好的路徑 */
    private int[] bestRoute;
    /** 最佳路徑總長度 */
    private int bestEvaluation;
    /** 禁忌表 */
    private int[][] tabuTable;
    /**禁忌表中的評估值*/
    private int[] tabuTableEvaluate;
    private DynamicDataWindow ddWindow;
    private long tp;

    public TabuSearchAlgorithm() {

    }

    /**
     * constructor of GA
     * 
     * @param n
     *            城市數量
     * @param g
     *            執行代數
     * @param c
     *            每次搜尋鄰居個數
     * @param m
     *            禁忌長度
     * 
     **/
    public TabuSearchAlgorithm(int n, int g, int c, int m) {
        nodeNum = n;
        MAX_GEN = g;
        neighbourhoodNum = c;
        tabuTableLength = m;
    }

    /**
     * 初始化Tabu演算法類
     * 
     * @param filename
     *            資料檔名,該檔案儲存所有城市節點座標資料
     * @throws IOException
     */
    private void init(String filename) throws IOException {
        // 讀取資料
        int[] x;
        int[] y;
        String strbuff;
        FileReader fileReader = new FileReader(filename);
        BufferedReader data = new BufferedReader(fileReader);
        nodeDistance = new int[nodeNum][nodeNum];
        x = new int[nodeNum];
        y = new int[nodeNum];
        String[] strcol;
        for (int i = 0; i < nodeNum; i++) {
            // 讀取一行資料,資料格式1 6734 1453
            strbuff = data.readLine();
            // 字元分割
            strcol = strbuff.split(" ");
            x[i] = Integer.valueOf(strcol[1]);// x座標
            y[i] = Integer.valueOf(strcol[2]);// y座標
        }
        data.close();
        // 計算距離矩陣
        // ,針對具體問題,距離計算方法也不一樣,此處用的是att48作為案例,它有48個城市,距離計算方法為偽歐氏距離,最優值為10628
        for (int i = 0; i < nodeNum - 1; i++) {
            nodeDistance[i][i] = 0; // 對角線為0
            for (int j = i + 1; j < nodeNum; j++) {
                double rij = Math
                        .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])
                                * (y[i] - y[j])) / 10.0);
                // 四捨五入,取整
                int tij = (int) Math.round(rij);
                if (tij < rij) {
                    nodeDistance[i][j] = tij + 1;
                    nodeDistance[j][i] = nodeDistance[i][j];
                } else {
                    nodeDistance[i][j] = tij;
                    nodeDistance[j][i] = nodeDistance[i][j];
                }
            }
        }
        nodeDistance[nodeNum - 1][nodeNum - 1] = 0;

        route = new int[nodeNum];
        bestRoute = new int[nodeNum];
        bestEvaluation = Integer.MAX_VALUE;
        tabuTable = new int[tabuTableLength][nodeNum];
        tabuTableEvaluate=new int[tabuTableLength];
        for (int i = 0; i < tabuTableEvaluate.length; i++) {
            tabuTableEvaluate[i]=Integer.MAX_VALUE;
        }
    }

    /** 生成初始群體 */
    void generateInitGroup() {
        System.out.println("1.生成初始群體");
        boolean iscontinue = false;
        for (int i = 0; i < route.length; i++) {
            do {
                iscontinue = false;
                route[i] = (int) (Math.random() * nodeNum);
                for (int j = i - 1; j >= 0; j--) {
                    if (route[i] == route[j]) {
                        iscontinue = true;
                        break;
                    }
                }
            } while (iscontinue);
            // System.out.println("i="+i+", route[i]="+route[i]);
        }
    }

    /** 複製編碼體,複製Gha到Ghb */
    public void copyGh(int[] Gha, int[] Ghb) {
        for (int i = 0; i < nodeNum; i++) {
            Ghb[i] = Gha[i];
        }
    }

    /** 計算路線的總距離 */
    public int evaluate(int[] chr) {
        // 0123
        int len = 0;
        // 編碼,起始城市,城市1,城市2...城市n
        for (int i = 1; i < nodeNum; i++) {
            len += nodeDistance[chr[i - 1]][chr[i]];
        }
        // 城市n,起始城市
        len += nodeDistance[chr[nodeNum - 1]][chr[0]];
        return len;
    }

    /**
     * 隨機獲取鄰域路徑
     * @param route 當前路徑
     * */
    public int[] getNeighbourhood(int[] route) {
        int temp;
        int ran1, ran2;
        int[] tempRoute=new int[route.length];
        copyGh(route, tempRoute);
        ran1 = (int) (Math.random() * nodeNum);
        do {
            ran2 = (int) (Math.random() * nodeNum);
        } while (ran1 == ran2);
        temp = tempRoute[ran1];
        tempRoute[ran1] = tempRoute[ran2];
        tempRoute[ran2] = temp;
        return tempRoute;
    }

    /**
     * 隨機獲取一定數量的領域路徑
     * */
    public int[][] getNeighbourhood(int[] route, int tempNeighbourhoodNum) {
        int[][] NeighbourhoodRoutes=new int[tempNeighbourhoodNum][nodeNum];
        List<int[]> tempExchangeNodeList=new ArrayList<>();
        int temp;
        int ran0, ran1;
        int[] tempRoute=null;
        boolean iscontinue;
        for(int i=0; i<tempNeighbourhoodNum; i++) {
            tempRoute=new int[route.length];
            copyGh(route, tempRoute);
            do{
                iscontinue=false;
                //隨機生成一個鄰域;
                ran0 = (int) (Math.random() * nodeNum);
                do {
                    ran1 = (int) (Math.random() * nodeNum);
                } while (ran0 == ran1);
                //判斷是否重複
                for (int j = 0; j <tempExchangeNodeList.size(); j++) {
                    if (tempExchangeNodeList.get(j)[0]<tempExchangeNodeList.get(j)[1]) {
                        if ((ran0 < ran1 && (tempExchangeNodeList.get(j)[0]==ran0 && tempExchangeNodeList.get(j)[1]==ran1))
                                ||(ran0 > ran1 && (tempExchangeNodeList.get(j)[0]==ran1 && tempExchangeNodeList.get(j)[1]==ran0))) {
                            iscontinue=true;
                        } 
                    }else {
                        if ((ran0 < ran1 && (tempExchangeNodeList.get(j)[0]==ran1 && tempExchangeNodeList.get(j)[1]==ran0))
                                ||(ran0 > ran1 && (tempExchangeNodeList.get(j)[0]==ran0 && tempExchangeNodeList.get(j)[1]==ran1))) {
                            iscontinue=true;
                        }
                    }
                }
                if (iscontinue==false) {
                    temp = tempRoute[ran0];
                    tempRoute[ran0] = tempRoute[ran1];
                    tempRoute[ran1] = temp;
                    tempExchangeNodeList.add(new int[]{ran0,ran1});//將交換點新增到列表中用於查重;
                    //判斷是否與route相同
                    for (int j = 0; j < tempRoute.length; j++) {
                        if (tempRoute[j]!=route[j]) {
                            iscontinue=false;
                        }
                    }
                    if (iscontinue==false && !isInTabuTable(tempRoute)) {
                        NeighbourhoodRoutes[i]=tempRoute;
                    }else {
                        iscontinue=true;
                    }
                }
            }while(iscontinue);
        }
        return NeighbourhoodRoutes;
    }

    /** 判斷路徑是否在禁忌表中 */
    public boolean isInTabuTable(int[] tempRoute) {
        int i, j;
        int flag = 0;
        for (i = 0; i < tabuTableLength; i++) {
            flag = 0;
            for (j = 0; j < nodeNum; j++) {
                if (tempRoute[j] != tabuTable[i][j]){
                    flag = 1;// 不相同
                    break;
                }
            }
            if (flag == 0){// 相同,返回存在相同
                break;
            }
        }
        if (i == tabuTableLength){// 不等
            return false;// 不存在
        } else {
            return true;// 存在
        }
    }

    /** 解禁忌與加入禁忌,注意禁忌策略的選擇 */
    public void flushTabuTable(int[] tempGh) {
        int tempValue=evaluate(tempGh);
        // 找到禁忌表中路徑的最大值;
        int tempMax=tabuTableEvaluate[0];
        int maxValueIndex=0;
        for (int i = 0; i < tabuTableLength; i++) {
            if(tabuTableEvaluate[i]>tempMax){
                tempMax=tabuTableEvaluate[i];
                maxValueIndex=i;
            }
        }
        // 新的路徑加入禁忌表
        if (tempValue<tabuTableEvaluate[maxValueIndex]) {
            if (tabuTableEvaluate[maxValueIndex]<Integer.MAX_VALUE) {
                copyGh(tabuTable[maxValueIndex], route);
            }
            System.out.println("測試點:更新禁忌表,maxValueIndex= "+maxValueIndex);
            for (int k = 0; k < nodeNum; k++) {
                tabuTable[maxValueIndex][k] = tempGh[k];
            }
            tabuTableEvaluate[maxValueIndex]=tempValue;
        }
    }
    /**啟動禁忌搜尋*/
    public void startSearch() {
        int nn;
        int neighbourhoodEvaluation;
        int currentBestRouteEvaluation;
        /** 存放鄰域路徑 */
        int[] neighbourhoodOfRoute = new int[nodeNum];
        /** 當代最好路徑 */
        int[] currentBestRoute = new int[nodeNum];
        /** 當前代數 */
        int currentIterateNum = 0;
        /** 最佳出現代數 */
        int bestIterateNum = 0;
        int[][] neighbourhoodOfRoutes=null;
        //用於控制迭代次數
        int[]priviousRoute=new int[nodeNum];
        // 初始化編碼Ghh
        generateInitGroup();
        // 將當前路徑作為最好路徑
        copyGh(route, bestRoute);
        currentBestRouteEvaluation=evaluate(route);
        bestEvaluation = currentBestRouteEvaluation;
        System.out.println("2.迭代搜尋....");
        while (currentIterateNum < MAX_GEN) {
            for (int i = 0; i < route.length; i++) {
                priviousRoute[i]=route[i];
            }
            neighbourhoodOfRoutes=getNeighbourhood(route, neighbourhoodNum);
            System.out.println("測試點:currentIterateNum= "+currentIterateNum);
            for(nn = 0; nn < neighbourhoodNum; nn++) {
                // 得到當前路徑route的一個鄰域路徑neighbourhoodOfRoute
//              neighbourhoodOfRoute=getNeighbourhood(route);
                neighbourhoodOfRoute=neighbourhoodOfRoutes[nn];
                neighbourhoodEvaluation = evaluate(neighbourhoodOfRoute);
//              System.out.println("測試:neighbourhoodOfRoute="+neighbourhoodEvaluation);
                if (neighbourhoodEvaluation < currentBestRouteEvaluation) {
                    copyGh(neighbourhoodOfRoute, currentBestRoute);
                    currentBestRouteEvaluation = neighbourhoodEvaluation;
//                  System.out.println("測試:neighbourhoodOfRoute="+neighbourhoodEvaluation);
                }
            }
            if (currentBestRouteEvaluation < bestEvaluation) {
                bestIterateNum = currentIterateNum;
                copyGh(currentBestRoute, bestRoute);
                bestEvaluation = currentBestRouteEvaluation;
                System.out.println("測試:currentBestRouteEvaluation="+currentBestRouteEvaluation);
            }
            copyGh(currentBestRoute, route);
            // 解禁忌表,currentBestRoute加入禁忌表
//          System.out.println("測試點:currentBestRoute= "+currentBestRoute);
            flushTabuTable(currentBestRoute);
            currentIterateNum++;
            for (int i = 0; i < priviousRoute.length; i++) {
                if (priviousRoute[i] != route[i]) {
                    currentIterateNum=0;
                    break;
                }
            }

            printRunStatus();
        }


        //結果顯示:
        System.out.println("最佳長度出現代數:");
        System.out.println(bestIterateNum);
        System.out.println("最佳長度:");
        System.out.println(bestEvaluation);
        System.out.println("最佳路徑:");
        for (int i = 0; i < nodeNum; i++) {
            System.out.print(bestRoute[i] + ",");
        }
    }

    /** 
     * @Description: 輸出結執行狀態
     */  
    private void printRunStatus() { 
        long millis=System.currentTimeMillis();
        if (millis-tp>20) {
            tp=millis;
            ddWindow.addData(millis, bestEvaluation);
        }
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        System.out.println("Start....");
        TabuSearchAlgorithm tabu = new TabuSearchAlgorithm(48, 120, 500, 100);
        tabu.ddWindow=new DynamicDataWindow("禁忌搜尋演算法優化求解過程");
        tabu.ddWindow.setVisible(true);
        tabu.init("C:\\Users\\att48.txt");
        tabu.startSearch();
    }

}

總結

啟發式搜尋演算法蘊含著許多人生哲學,它雖不是數學方法,其思想更類似於人類解決問題的思想和一些人生中總結的道理,值得好好體會。最後用網上一段描述各種搜尋演算法的例子來作為總結:

為了找出地球上最高的山,一群有志氣的兔子們開始想辦法。
(1)兔子朝著比現在高的地方跳去。他們找到了不遠處的最高山峰。但是這座山不一定是珠穆朗瑪峰。這就是爬山法,它不能保證區域性最優值就是全域性最優值。
(2)兔子喝醉了。他隨機地跳了很長時間。這期間,它可能走向高處,也可能踏入平地。但是,他漸漸清醒了並朝他踏過的最高方向跳去。這就是模擬退火。
(3)兔子們知道一個兔的力量是渺小的。他們互相轉告著,哪裡的山已經找過,並且找過的每一座山他們都留下一隻兔子做記號。他們制定了下一步去哪裡尋找的策略。這就是禁忌搜尋。
(4)兔子們吃了失憶藥片,並被髮射到太空,然後隨機落到了地球上的某些地方。他們不知道自己的使命是什麼。但是,如果你過幾年就殺死一部分海拔低的兔子,多產的兔子們自己就會找到珠穆朗瑪峰。這就是遺傳演算法。

參考資料

1.爬山法
2.區域性搜尋案例與求解方法
3.《群體智慧優化演算法及其應用》雷秀娟 著。