禁忌搜索算法

1.局部搜索算法

a.定義

step1:選定一個初始可行解:x0;記錄當前最優解:xbest = x0,T = N( xbest )
step2:當T \ ( xbest ) = 空集時,或者滿足其他停止運算準則時,輸出計算結果,停止運算;否則,從T中選一集合S,得到S中的最好接xnow;若f ( xnow ) < f ( xbest ),則xbest = xnow,T = N ( xbest );否則,T : = T \ S;重複step2。

b.方法1--全鄰域搜索(以五城市對稱TSP問題爲例)

一、圖:

二、算法描述(以五城市對稱TSP問題爲例):

初始:xbest = (A,B,C,D,E),f( xbest )=45,本例中,定義鄰域映射爲對換2個城市的位置。S爲N( xbest )全集。

進入循環:

循環1:N(xbest)={(ABCDE),(ACBDE),(ADCBE),(AECDB),(ABDCE),(ABEDC),(ABCED)},對應目標函數爲f(x)={45, 43, 45, 60, 60, 59, 44}

判定條件:xbest=(ACBDE),f(x)=43。xbest與初始狀態不同,N(xbest) \ (xbest) != 空集

循環2:N(xbest)={(ACBDE),(ABCDE),(ADBCE),(AEBDC),(ACDBE),(ACEDB),(ACBED)},對應目標函數爲f(x)={43, 45, 44, 59, 59, 58, 43}

判定條件:xbest=(ACBDE),f(x)=43。xbest與初始狀態相同,N(xbest)與循環1相同,N(xbest) \ (xbest) = 空集,跳出循環

得解xbest=(ACBDE),f(xbest)=43。


三、代碼:

  1. void localsearchWNS(const vector< vector<int> > &v,vector<int> &x,int &costbest,int firstcity)  
  2. {  
  3.     swap(x[0],x[firstcity]);  
  4.     costbest=countDis(x,v);//countDis用於計算花費,代碼在此不再貼出  
  5.     vector<int> tempx=x;  
  6.     int num=x.size();     
  7.     while(true)  
  8.     {  
  9.         int min=costbest;  
  10.         for(int i=1;i<num-1;i++)  
  11.         {     
  12.             for(int j=i+1;j<num;j++)  
  13.             {  
  14.                 swap(x[i],x[j]);  
  15.                 int temp=countDis(x,v);  
  16.                 if(temp<min)  
  17.                 {  
  18.                     min=temp;  
  19.                     tempx=x;  
  20.                 }  
  21.                 swap(x[i],x[j]);  
  22.             }     
  23.         }  
  24.         if(min==costbest)  
  25.         {  
  26.             break;  
  27.         }  
  28.         costbest=min;  
  29.         x=tempx;  
  30.     }     
  31. }  

四、實驗:

實驗所得,當n<9(比較小的時候),所求與全局最優解相同(回溯求得),在n>10之後,開始有偏離,並且偏離程度不可預測。除起點A外,解空間樹共4!=24種情況,以上搜索只搜索了12種,故局部搜索算法<全鄰域搜索>並沒有搜索完整棵解空間樹,求得也不是全局最優解,而是局部最優解。用局部最優解代替全局最優解。

c.方法2--一步隨機搜索

一、算法描述:

初始:xbest = (A,B,C,D,E),f( xbest )=45,本例中,定義鄰域映射爲對換2個城市的位置。

進入循環:

循環x:從N(xbest)中隨機選擇一點xnow,如果f(xnow)<f(xbest),則xbest=xnow,且重新構建N(xbest);如果f(xnow)>=f(xbest),則從N(xbest)中移除點xnow,繼續循環

判定條件:N(xbest) = 空集,跳出循環

得解

二、代碼:

  1. void localsearchOSRS(const vector< vector<int> > &v,vector<int> &x,int &costbest,int firstcity)  
  2. {  
  3.     swap(x[0],x[firstcity]);  
  4.     costbest=countDis(x,v);  
  5.     vector< vector<int> > tempx;  
  6.     int num=x.size();     
  7.     int flag=0;  
  8.     while(true)  
  9.     {  
  10.         if(flag==0)  
  11.         {  
  12.             for(int i=1;i<num-1;i++)  
  13.             {     
  14.                 for(int j=i+1;j<num;j++)  
  15.                 {  
  16.                     swap(x[i],x[j]);  
  17.                     tempx.push_back(x);  
  18.                     swap(x[i],x[j]);  
  19.                 }     
  20.             }  
  21.         }  
  22.         int n=random(0,tempx.size()-1);  
  23.         int cost=countDis(tempx[n],v);    
  24.         if(cost<costbest)  
  25.         {  
  26.             costbest=cost;  
  27.             x=tempx[n];  
  28.             vector< vector<int> > clearv;  
  29.             tempx=clearv;  
  30.             flag=0;  
  31.         }  
  32.         else  
  33.         {  
  34.             if(tempx.size()>0)  
  35.             {  
  36.                 tempx.erase(tempx.begin()+n);  
  37.                 if(tempx.size()==0)  
  38.                 {  
  39.                     return;  
  40.                 }  
  41.                 flag=1;  
  42.             }  
  43.         }  
  44.     }  
  45. }   

三、實驗:

實驗所得n=7時就與最優解開始偏離,偏離程度不可預計,與初始可行解、鄰域結構有關

d.分析

局部最優解偏離全局最優解的程度,一部分上取決於初始可行解的選擇,一部分取決於鄰域結構的選擇。因此在使用局部搜索算法時,爲了得到好的解,可以比較不同的鄰域結構(比如,ABCDE,除初始點外,其餘點順序右移。好的鄰域結構的作用是:通過此操作,改變序列,形成鄰域,達到搜索更小的範圍,卻搜到更接近全局最優解的局部最優解的目的)和不同的初始點。一個非常直觀的結論是:如果初始點的選擇足夠多,總可以計算出全局最優解。

2.禁忌搜索

a.定義

step1:給以禁忌表H=空集,並選定一個初始解xnow;

step2:滿足停止規則時,停止計算,輸出結果;否則,在xnow的領域N(xnow)中選擇不受禁忌的候選集Can_N(xnow);在Can_N(xnow)中選一個評價值最佳的解xnext,xnow=xnext;更新歷史記錄H,保存f(xnow),重複step2;

step3:在保存的衆多f中,挑選最小(大)值作爲解;

b.思想

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

比喻:兔子們找到了泰山,它們之中的一隻就會留守在這裏,其他的再去別的地方尋找。就這樣,一大圈後,把找到的幾個山峯一比較,珠穆朗瑪峯脫穎而出。

c.基本概念

禁忌對象:禁掉誰?(根據受禁對象的不同選擇,可行解是一禁禁一個;還是一禁禁掉一大片。主要對禁忌範圍,及搜索範圍有影響

禁忌長度:禁多久?(禁掉的東西什麼時候放出來?禁忌長度過短,會導致循環;禁忌長度過長,會導致計算時間過長。主要對解的收斂速度有影響

禁忌表:包括禁忌對象和禁忌長度(總的來說就是:怎麼禁?)

候選集:鄰域中可行解的選取?(候選集的大小,過大增加計算內存和計算時間,過小過早陷入局部最優)

d.小栗子(以四城市非對稱TSP爲例)

一、圖:

二、過程模擬(以四城市非對稱TSP爲例):

禁忌長度爲3(等於候選集個數)


禁忌長度是3(等於候選集中對換個數)的缺點在於:如cd對換一直從步2禁忌到結束,雖然在步2與步1銜接時,防止了回到原先候選集的可能,但也同時抹除了後續ACBD->ADCB,ADCB->ACBD,ACDB->ADCB,ADCB->ACDB的可能。

將禁忌長度改爲2,上述步驟一樣


從禁忌長度爲2的迭代與禁忌長度爲3的迭代比較可得:
禁忌長度短會造成循環,也就可能在一個局部最優解的附近循環(步6,產生ABCD循環到步1)(極限狀況下,禁忌長度爲1:造成一直在一個局部最優解處循環,退化爲局部搜索算法)(此處由於城市個數只有4個,除去起點A,BCD排列一共才6種,所以看上去好像<循環會產生的足夠晚,會比禁忌長度更長的方案產生更多的候選局部最優解>。但如果城市個數是20呢?禁忌長度爲1,是否會在第5,6步就產生循環呢?相對而言禁忌長度爲(19-1)*(19)/2的方案,會迭代(19-1)*(19)/2步,產生更多候選局部最優解)。禁忌長度長會造成,算法的記憶存儲量增加,使得算法的計算時間增加(譬如,上述例子,20個城市的TSP,從頭禁到尾,一定要跑(19-1)*(19)/2步,但是禁忌長度短,或許早就出現循環,解收斂了)。

三、代碼(禁忌長度等於禁忌對象個數):

  1. #include"tsp.h"  
  2. using namespace std;  
  3. bool cmp(int a,int b)  
  4. {  
  5.     return a<b;  
  6. }  
  7. bool countMin(const vector< vector<int> > &v,vector<int> &x,vector< vector<int> > &tabutable,const int &num)  
  8. {  
  9.     int xtemp;  
  10.     int ytemp;  
  11.     int min=INT_MAX;  
  12.     for(int i=1;i<num-1;i++)  
  13.     {     
  14.         for(int j=i+1;j<num;j++)  
  15.         {  
  16.             swap(x[i],x[j]);  
  17.             int temp=countDis(x,v);  
  18.             if(temp<min&&tabutable[x[i]][x[j]]==0&&tabutable[x[j]][x[i]]==0)  
  19.             {  
  20.                 min=temp;  
  21.                 xtemp=i;  
  22.                 ytemp=j;  
  23.             }  
  24.             swap(x[i],x[j]);  
  25.         }     
  26.     }  
  27.     if(min==INT_MAX)  
  28.     {  
  29.         return false;  
  30.     }  
  31.     else  
  32.     {  
  33.         swap(x[xtemp],x[ytemp]);  
  34.         tabutable[x[xtemp]][x[ytemp]]=1;  
  35.         tabutable[x[ytemp]][x[xtemp]]=1;  
  36.         return true;  
  37.     }  
  38. }  
  39.   
  40. void tabusearchN(const vector< vector<int> > &v,vector<int> &x,int &costbest,int firstcity)  
  41. {  
  42.     swap(x[0],x[firstcity]);  
  43.     int num=x.size();//城市節點個數  
  44.     vector< vector<int> > tabutable;//禁忌表,tabutable[i][j]=z表示對換對(i,j)的禁忌長度爲z   
  45.     vector<int> f;//記錄每一個局部最優解   
  46.     initX(tabutable,num);  
  47.       
  48.     for(int i=0;i<num;i++)//初始化禁忌長度爲0   
  49.     {  
  50.         for(int j=i+1;j<num;j++)  
  51.         {  
  52.             tabutable[x[i]][x[j]]=0;  
  53.             tabutable[x[j]][x[i]]=0;  
  54.         }  
  55.     }  
  56.     f.push_back(countDis(x,v));  
  57.     while(countMin(v,x,tabutable,num))  
  58.     {  
  59.         f.push_back(countDis(x,v));   
  60.     }  
  61.     sort(f.begin(),f.end(),cmp);  
  62.     costbest=f[0];  
  63. }  

考慮:禁忌長度的選擇?候選集的選擇?禁忌對象(禁忌表中被禁的那些變化元素)的選擇?評價值的替代形式?終止原則怎樣給出?

e.技術問題(解決上述<考慮>)

零、靠近全局最優解與計算時間長短的影響因素

1.是由禁忌長度(禁多久)的選擇、禁忌對象(禁掉誰)的選擇、候選集的選擇、評價值的形式等綜合影響的。

如以下禁忌對象的選擇中,解的簡單變化比(解的分量變化和目標值變化)的禁忌範圍要小,這可能造成計算時間的增加,但它也給予了較大的搜索範圍。但這只是給予了更大的搜索範圍,並不代表它能比其他兩種方式獲得更好的解。比如在此同時,它的禁忌長度過短,導致過早陷入某個局部最優解的循環,那麼再大的搜索範圍也是沒用的。

總的而言,對於上述因素的考慮,對於靠近全局最優解的考慮。它是一個搜索範圍以及是否會過早陷入局部最優解的一個博弈。

2.計算時間長短主要體現在對解的評估,即計算目標值。

一、狀態變化和禁忌對象的選擇(對受禁範圍的影響)

0)例子背景

初始解:xnow = (A,B,C,D,E),f( xnow)=45,N(xnow)鄰域的構造:定義鄰域映射爲對換2個城市的位置。候選集Can_N(xnow)是從N(xnow)中選擇除初始解外的5個可行解。

1)解的簡單變化

x,y∈D(解的定義域)。簡單解變化,x->y∈N(x)(x的鄰域)。即在x的鄰域中選擇(體現在禁忌對象上就是禁掉)一個解了事。這種手段在兩種局部搜索方法中都用過。這種變化將問題的解看成變化的最基本因素。

以上述背景爲例:

N(xnow)={(ACBDE),(ADCBE),(AECDB),(ABDCE),(ABCED)},對應目標函數爲f(x)={ 43, 45, 60, 60, 44}。比較選擇f值最低的(ACBDE)作爲xnext,並且將上一個xnow(A,B,C,D,E)拉入禁忌表,H={ ( A , B , C , D , E ; 45 ) }。注意是將一個可行解列爲禁忌對象。

2)向量分量變化

(x0,x1,x2,x3....xn)->(x0,x1,y,x3...xn),這種變化方式在解空間樹爲子集樹的向量上,體現爲其中幾個xi取反(0-1揹包),變化數目爲2^n;在解空間樹爲排列樹的向量上,體現爲swap其中兩個元素,或多個元素(2-opt,TSP),變化數目爲n!。體現在禁忌對象上就是禁掉一個交換對或者一種取反方式。這種變化以問題解向量的每個分量變化爲基礎因素

以上述背景爲例:

N(xnow)={(ACBDE),(ADCBE),(AECDB),(ABDCE),(ABCED)},對應目標函數爲f(x)={ 43, 45, 60, 60, 44}。比較選擇f值最低的(ACBDE)作爲xnext,並且將上一個xnow(A,B,C,D,E)到現在的xnext(A,C,B,D,E)之間的交換對(B,C)拉入禁忌表,H={ ( B,C) }。注意是將一個交換對列爲禁忌對象。

3)目標值變化

H( a ) = { x ∈ D | f( x ) = a },即禁掉所有目標值爲a的可行解。如此一來倘若a變成b,那麼禁掉的目標變化的就是整整一個目標值爲x的集合!

以上述背景爲例:

N(xnow)={(ACBDE),(ADCBE),(AECDB),(ABDCE),(ABCED)},對應目標函數爲f(x)={ 43, 45, 60, 60, 44}。比較選擇f值最低的(ACBDE)作爲xnext,並且將上一個xnow(A,B,C,D,E)的目標值45禁忌掉,H={ ( 45) }。注意是將一個目標值列爲禁忌對象。

4)分析

控制其他變量,單就禁忌對象的選擇和狀態變化而言。解的簡單變化比(解的分量變化和目標值變化)的禁忌範圍要小,這可能造成計算時間的增加,但它也給予了較大的搜索範圍;(解的分量變化和目標值變化)比解的簡單變化的禁忌範圍要大,這減少了計算時間,可能引發的問題是禁忌範圍太大已至於陷在局部最優點。

受禁範圍小,計算時間長,搜索範圍大,易找出全局最優;受禁範圍打,計算時間短,搜索範圍小,易陷入局部最優。

二、禁忌長度的選擇(對受禁時間的影響)

 1)分析

控制其他變量,單就禁忌長度的選擇而言,禁忌長度越短,機器內存佔用越少,解禁範圍更大(搜索範圍上限越大),但很容易造成搜索循環(實際去搜索的範圍卻很小),過早陷入局部最優。禁忌長度過長又會導致計算時間過長(具體例子見上粉字分析)

2)禁忌長度的選取方案

(1)t 可以爲常數,易於實現。
(2)t∈[ tmin , tmax ],t 變化的,tmin和tmax是確定的。tmin和tmax根據問題的規模確定,t 的大小主要依據實際問題、實驗和設計者的經驗。
(3) tmin和tmax的動態選擇。

三、特赦規則

通俗定義:對於在禁忌的對象,如果出現以下情況,不論現在對象的禁忌長度如何,均設爲0

(1)基於評價值的規則,若出現一個解的目標值好於前面任何一個最佳候選解,可特赦;
(2)基於最小錯誤的規則,若所有對象都被禁忌,特赦一個評價值最小的解;

(3)基於影響力的規則,可以特赦對目標值影響大的對象。

四、候選集

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

五、評價函數

評價函數分爲直接直接評價函數和簡介評價函數

直接評價函數:上述例子,均直接使用目標值作爲評價函數

間接評價函數:反映目標函數特性的函數(會比目標函數的計算更爲簡便,用以減少計算時間等)

六、終止規則

禁忌算法是一個啓發式算法,我們不可能讓禁忌長度充分大,所以一些直觀的終止規則就出現了

(1)確定步數終止,無法保證解的效果,應記錄當前最優解;

(2)頻率控制原則,當某一個解、目標值或元素序列的頻率超過一個給定值時,終止計算;

(3)目標控制原則,如果在一個給定步數內,當前最優值沒有變化,可終止計算。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章