1. 程式人生 > >遺傳演算法求解TSP問題

遺傳演算法求解TSP問題

遺傳演算法是一種啟發式搜尋,屬於進化演算法的一種。它最初是人們根據自然界對物種的自然選擇和遺傳規律而設計的。它模擬自然界物種的自然選擇、遺傳和變異等,對一個種群的基因進行改良。

遺傳演算法需要設定交叉概率、變異概率和迭代次數等引數,且演算法的收斂性受其引數設定影響較大。
遺傳演算法中把每一個候選解看做是一個個體,個體組成的集合看作是一個種群。遺傳演算法通過對每個解進行二進位制編碼把每個解轉化為0-1字串,其中每一個位叫做一個基因。不同基因的組合構成的個體的表現型也不同。它從一個初始種群開始,經過N次迭代最終求得近似最優解。在每次迭代的過程中,他首先計算初始種群內的每個個體的適應度,然後對初始種群使用選擇運算元,遺傳運算元,變異運算元進行操作。每一個運算元操作的過程都不是固定的,而是以一定的概率操作。這樣確保了演算法的搜尋空間可以覆蓋整個解空間,同時也能條出局部最優解。


演算法終止條件有兩種,一種是在連續w次迭代之後當前最優解未發生變化就終止,其中w為根據經驗設定的閾值,通常與問題規模有關;另一種是當演算法迭代到一定的次數後終止。

TSP問題解的編碼

       編碼是應用遺傳演算法時要解決的首要問題,也是設計遺傳演算法的一個關鍵步驟。編碼方法除了決定個體的染色體排列形式之外,它還決定了個體從搜尋空間的基因型變換到解空間的表現型時的解碼方法。編碼方法也影響到交叉運算元、變異運算元等遺傳運算元的運算方法。由此可見,編碼方法在很大程度上決定了如何進行群體的遺傳進化運算以及遺傳進化運算的效率。

遺傳演算法編碼的方式有多種,比如:二進位制編碼、浮點數編碼和符號編碼。針對不同的問題選擇合適的編碼方法有助於問題的求解。基於二進位制的編碼對TSP問題來說,我們無法保證執行完交叉變異變換後的解滿足題意,故舍棄此方法。基於浮點數的編碼方法通常用於求解連續函式的近似最優函式值,對於離散的TSP問題並不適合。在TSP問題中每一個解就是一個由城市序號組成的路徑序列,在演算法中也叫作種群中的個體。根據TSP問題的情況,我們選擇符號編碼的方法。舉個例子:有4個城市,城市序號為:0,1,2,3。假設從0出發依次經過城市1,2,3最後回到城市0的路徑最短。那麼0-1-2-3-0就是這4個城市TSP問題的一個最優解,也叫作一個個體。路徑序列中的單個城市序號,比如2,叫做個體的一個基因。通常最優解路徑只有一條,但是路徑序列不止一個,比如1-2-3-0-1,他們僅僅是起點不同。

適應度計算

 在自然界中,根據遺傳學的觀點,適應度越高的個體越有機會被選擇,進而保留其基因。因此在遺傳演算法的設計過程必須對適應度具有正反饋的選擇策略。在選擇策略制定之前需要考慮衡量每個個體適應度的方法。根據TSP問題的目標,不難得出路徑代價越小其適應度越高,同時適應度值必須為正值。因此取適應度為路徑代價的倒數。

選擇操作:輪盤賭+精英選擇

INPUT:初始種群Popu[M]
OUTPUT:選擇出種群NewPopu[M]
Begin
	Sumfit=0.0;
	For(k=0;k<Popu.Size();k++)
		Sumfit+=Popu[k].fit;
	End For
	For(i=0,k=0;i<M-1;i++)
	P=Random()%1000/1000.0;//隨機產生一個概率
	SumP=0;
	For(j=0;j<Popu.Size();j++)
		SumP=SumP+(Popu[j].fit)/Sumfit;
		If(SumP>P) 
			Break;
		End If
	End For
	NewPopu[k++]=Popu[j];
End For
Sort(Popu)//按適應度從大到小排序
NewPopu[k]=Popu[0];//精英放在最後
End

交叉策略

在生物的自然進化過程中,兩個同源染色體通過交配而重組,形成新的染色體, 從而產生出新的個體或物種。交配重組是生物遺傳和進化過程中的一個主要環節。模仿這個環節,在遺傳演算法中也使用交叉運算元來產生新的個體。遺傳演算法中的所謂交叉運算,是指對兩個相互配對的染色體按某種方式相互交換其部分基因,從而形成兩個新的個體。對TSP問題來說,交叉運算不僅僅是互換兩個個體的部分基因,同時還必須確保在互換基因後產生的新個體必須無重複的包含所有的城市。這是TSP交叉運算設計的一個難點和重點,同時並不是對每個個體都執行交叉操作,而是按照一定的交叉概率PC進行。

遺傳演算法中通常的交叉演算法有單點交叉、多點交叉、均勻交叉和算術交叉。單點交叉又稱為簡單交叉,它是指在個體編碼串中只隨機設定一個交叉點,然後在該點相互交換兩個配對個體的部分染色體。多點交叉指在個體編碼串中隨機設定多個交叉點,然後進行基因交換。多點交叉又稱為廣義交叉。常見的是雙點交叉。指兩個配對個體的每一個基因座上的基因都以相同的交叉概率進行交換,從而形成兩個新的個體。均勻交叉實際上可歸屬於多點交叉的範圍。由兩個個體的線性組合而產生出兩個新的個體,常用於求函式值的遺傳演算法中。

Cross (Popu[M])//交叉策略
INPUT:選擇後的種群Popu[M]
OUTPUT:交叉後的種群NewPopu[M]
Begin
       For(i=0;i<Popu.Size()-1;i++)//最後一個個體為種群內最優個體無條件保留
       P=Random()%1000/1000.0;//隨機產生一個概率
       If(P<PC)//以概率PC交叉
              j=Random()%(Popu.Size()-1)
              Xcross(Popu[i],Popu[j])
              Fit(Popu[i]);
              Fit(Popu[j]);
              If(Popu[i].fit>Popu[M-1].fit)Swap(Popu[i],Popu[M-1]);
If(Popu[j].fit>Popu[M-1].fit)Swap(Popu[j],Popu[M-1]);
       EndIf
End For
End

整體來看,演算法P的概率對前M-1個元素進行配對交叉。如果交叉後比當前最優值Popu[M-1]適應度高,則與當前最優進行交換。這樣保證了種群內的最優個體(最後一個)總是能100%遺傳給下一代。交叉過後重新計算其適應度,為變異操作做準備。其中Xcross()函式是隨機生成兩個交叉點,然後交換交叉點內部的元素。其他元素按照填充法,從第二個交叉點迴圈將剩餘的元素新增到個體。變異後個體如果比當前最優值適應度高,則將當前最優與變異後個體交換。

以兩個父代個體為例:(1 2 3 4 5 6 7 8)和(2 4 6 8 7 5 3 1),隨機選擇兩個交叉的點,假如第一個點為位置3,第二個交叉點為位置5,那麼在兩個點之間的位置將進行交叉。在此例項中,第一個父代個體中3 4 5被選中,第二個父代個體中,6 8 7被選中。交換交叉區間內元素變為(1 2 6 8 7 6 7 8)和(2 4 3 4 5 5 3 1),然後從第二個交叉點開始,將原來相應的父代按照順序進行填充子代1從第6個元素開始,如果選擇的元素已經存在在該子代中,跳過該元素(6 7 8 跳過),選擇下一個元素,這種過程反覆進行,直到所有的城市都被選擇一次。最終變換後的子代為(4 5 6 8 7 1 2 3)和(8 7 3 4 5 1 2 6)。

變異策略

交叉運算是產生新個體的主要方法,它決定了遺傳演算法的全域性搜尋能力;而變異運算只是產生新個體的輔助方法,但它也是必不可少的一個運算步驟,因為它決定了遺傳演算法的區域性搜尋能力。交叉運算元與變異運算元的相互配合,共同完成對搜尋空間的全域性搜尋和區域性搜尋,從而使得遺傳演算法能夠以良好的搜尋效能完成最優化問題的尋優過程。

常見的變異操作主要有替換變異、交換變異、插入變異和簡單倒位變異等。替換變異就是先從父代個體中選擇一段基因,然後再隨機在剩下的基因中隨機選擇一個位置,並插入選中的基因段。交換變異就是所及選擇兩個基因,然後交換兩個基因的位置。插入變異屬於替換變異的特殊情況,只選擇一個基因插入而不是基因段。簡單倒位變異是隨機選擇一段基因然後將其逆序。本文選擇的是交換變異。對每個基因按照一定的概率進行變異選擇,若選中,則隨機從剩下的基因中選一個與其交換。


程式碼:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <fstream>
#include <iterator>
#include <cstring>
#define MAX 0x37777777
#define MAXCITY 300
#define RANGE 500
using namespace std;

struct Point
{
    int x;
    int y;
    char name;
};
struct Answer
{
    double cost;
    vector<int> path;
};

//遺傳引數
int CITYNUM=52;  //城市數
double PC=0.8 ;     //交叉概率
double PM=0.2 ;     //變異概率
int M=9   ;      //種群規模
int G=10000  ;   //迭代次數
double dist[MAXCITY][MAXCITY]={0};
Point citys[MAXCITY];//city's coordinate

//read the graph & compute the distance dist[i][j]
void InitGraph(int cityNum)
{
    fstream fileRead;
    char fileName[20];
    sprintf(fileName,"city_coor_%d.txt",cityNum);
    fileRead.open(fileName,ios::in);
    for(int i=0;i<cityNum;i++)
    {
        int temp;
        fileRead>>temp>>citys[i].x>>citys[i].y;
        //cout<<temp<<"--"<<citys[i].x<<"--"<<citys[i].y<<endl;
    }
    fileRead.close();
    memset(dist,0,sizeof(dist));
    for(int i=0;i<cityNum;i++)
    {
        for(int j=0;j<cityNum;j++)
        {
            if(j!=i&&dist[i][j]==0)
            {
                double d2=(citys[i].x-citys[j].x)*(citys[i].x-citys[j].x)+(citys[i].y-citys[j].y)*(citys[i].y-citys[j].y);
                dist[i][j]=dist[j][i]=sqrt(d2);
            }
        }
    }
}
void InitPopu(vector<Answer> &Popu)
{
    Answer answer;
    //generate population
    for(int i=0;i<CITYNUM;i++)
    {
        answer.path.push_back(i);
    }
    for(int i=0;i<M;i++)
    {
        int first=0,second=0;
        first=rand()%CITYNUM;
        second=rand()%CITYNUM;
        if(first>second)//promise first<=second
        {
            first=first^second;
            second=first^second;
            first=first^second;
        }
        //cout<<first<<"--"<<second<<endl;
        if(first!=second)
        {
            random_shuffle(answer.path.begin()+first,answer.path.begin()+second);
        }
        Popu.push_back(answer);
        //copy(Popu[i].path.begin(),Popu[i].path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
    }
}
void Cost(Answer &answer)
{
    double sum=0;
    for(int i=0;i+1<(answer.path).size();i++)
    {
        sum+=dist[(answer.path)[i]][(answer.path)[i+1]];
    }
    sum+=dist[(answer.path)[(answer.path).size()-1]][(answer.path)[0]];
    answer.cost=sum;
}
bool Cmp(const Answer & a1,const Answer & a2)
{
    return a1.cost<a2.cost;
}
void Choose(vector<Answer> &Popu)
{
    vector<Answer> newPopu;
    double sumFits=0;
    double fits=0;
    for(int j=0;j<Popu.size();j++)
    {
        sumFits+=(1.0/Popu[j].cost);
    }
    for(int i=0;i<M-1;i++)
    {
        double random=(rand()%998)/997.0;
        fits=0;
        int num=0;
        for(int j=0;j<Popu.size();j++)
        {
            fits+=(1.0/Popu[j].cost/sumFits);
            if(fits>=random)
            {
                num=j;//選中num
                break;
            }
        }
        newPopu.push_back(Popu[num]);
    }
    sort(Popu.begin(),Popu.end(),Cmp);
    newPopu.push_back(Popu[0]);//精英選擇,插在最後
    swap(Popu,newPopu);
}
void Cro1(Answer &a1,Answer &a2)//部分匹配交叉
{
    Answer a11=a1,a22=a2;
    int first=0,second=0;
    first=rand()%CITYNUM;
    second=rand()%CITYNUM;
    if(first>second)//promise first<=second
    {
        first=first^second;
        second=first^second;
        first=first^second;
    }
    int parent1[MAXCITY],parent2[MAXCITY];
    memset(parent1,0,sizeof(parent1));
    memset(parent2,0,sizeof(parent2));
    for(int j=first;j<second;j++)
    {
        parent1[a2.path[j]]=1;
        parent2[a1.path[j]]=1;
        //a1.path[j]=a1.path[j]^a2.path[j];
        //a2.path[j]=a1.path[j]^a2.path[j];
        //a1.path[j]=a1.path[j]^a2.path[j];
        int temp=a1.path[j];
        a1.path[j]=a2.path[j];
        a2.path[j]=temp;
        //a1.path[j]=(a1.path[j])+(a2.path[j]);
        //a2.path[j]=(a1.path[j])-(a2.path[j]);
        //a1.path[j]=(a1.path[j])-(a2.path[j]);
        //cout<< a1.path[j]<<" "<< a2.path[j]<<endl;
    }

    int k1=second,k2=second;
    for(int j=second;j<CITYNUM;j++)
    {
        //a1
        int cur=a11.path[j];
        if(parent1[cur]==0)
        {
            a1.path[k1++]=cur;
            if(k1>=CITYNUM)k1=0;
            parent1[cur]=1;
        }
        //a2
        cur=a22.path[j];
        if(parent2[cur]==0)
        {
            a2.path[k2++]=cur;
            if(k2>=CITYNUM)k2=0;
            parent2[cur]=1;
        }
    }


    for(int j=0;j<second;j++)
    {
        //a1
        int cur=a11.path[j];
        if(parent1[cur]==0)
        {
            a1.path[k1++]=cur;
            if(k1>=CITYNUM)k1=0;
            parent1[cur]=1;
        }
        //a2
        cur=a22.path[j];
        if(parent2[cur]==0)
        {
            a2.path[k2++]=cur;
            if(k2>=CITYNUM)k2=0;
            parent2[cur]=1;
        }
    }
    //copy(a1.path.begin(),a1.path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
    //copy(a2.path.begin(),a2.path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
}
void Cro2(Answer &a1,Answer &a2)//部分匹配交叉
{
    Answer a11=a1,a22=a2;
    int first=0,second=0;
    first=rand()%CITYNUM;
    second=rand()%CITYNUM;
    if(first>second)//promise first<=second
    {
        first=first^second;
        second=first^second;
        first=first^second;
    }
    int parent1[MAXCITY],parent2[MAXCITY];
    memset(parent1,-1,sizeof(parent1));
    memset(parent2,-1,sizeof(parent2));
    for(int j=first;j<second;j++)
    {
        parent1[a2.path[j]]=a1.path[j];
        parent2[a1.path[j]]=a2.path[j];
        //a1.path[j]=a1.path[j]^a2.path[j];
        //a2.path[j]=a1.path[j]^a2.path[j];
        //a1.path[j]=a1.path[j]^a2.path[j];
        int temp=a1.path[j];
        a1.path[j]=a2.path[j];
        a2.path[j]=temp;
    }

    int k1=second,k2=second;
    for(int j=second;j<CITYNUM;j++)
    {
        //a1
        int cur=a11.path[j];
        while(parent1[cur]!=-1)
        {
            cur=parent1[cur];
        }
        a1.path[k1++]=cur;
        if(k1>=CITYNUM)k1=0;
        //a2
        cur=a22.path[j];
        while(parent2[cur]!=-1)
        {
            cur=parent2[cur];
        }
        a2.path[k2++]=cur;
        if(k2>=CITYNUM)k2=0;
    }


    for(int j=0;j<first;j++)
    {
        //a1
        int cur=a11.path[j];
        while(parent1[cur]!=-1)
        {
            cur=parent1[cur];
        }
        a1.path[k1++]=cur;
        if(k1>=CITYNUM)k1=0;
        //a2
        cur=a22.path[j];
        while(parent2[cur]!=-1)
        {
            cur=parent2[cur];
        }
        a2.path[k2++]=cur;
        if(k2>=CITYNUM)k2=0;
    }
    //copy(a1.path.begin(),a1.path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
    //copy(a2.path.begin(),a2.path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
}
void Cross(vector<Answer> &newPopu)
{
    for(int i=0;i<newPopu.size()-1;i++)
    {
        double random=(rand()%998)/997.0;
        if(random<PC)
        {
            int first=i;
            int second=rand()%(newPopu.size()-1);
            //交叉
            Cro2(newPopu[first],newPopu[second]);
            Cost(newPopu[first]);
            Cost(newPopu[second]);
            if(newPopu[first].cost<newPopu[M-1].cost)swap(newPopu[first],newPopu[M-1]);
            if(newPopu[second].cost<newPopu[M-1].cost)swap(newPopu[second],newPopu[M-1]);
        }
    }
}
void Variation(vector<Answer> &newPopu)
{
    for(int i=0;i<newPopu.size()-1;i++)
    {
        double random=(rand()%998)/997.0;
        if(random<PM)
        {
            int first=rand()%CITYNUM;
            int second=rand()%CITYNUM;
            //變異
            int temp=newPopu[i].path[first];
            newPopu[i].path[first]=newPopu[i].path[second];
            newPopu[i].path[second]=temp;
            Cost(newPopu[i]);
            if(newPopu[i].cost<newPopu[M-1].cost)swap(newPopu[i],newPopu[M-1]);
        }
    }
}
void Save(Answer answer)
{
    fstream fileWrite;
    fileWrite.open("out.txt",ios::out|ios::app);
    fileWrite<<"城市個數:"<<CITYNUM<<";種群規模:"<<M<<";交叉概率:"<<PC<<";變異概率:"<<PM<<";代數:"<<G<<endl;
    fileWrite<<"最小代價:"<<answer.cost<<endl;
    fileWrite.close();
}
double GA()
{
    vector<Answer> Popu;
    InitGraph(CITYNUM);//read the citys & compute dist
    InitPopu(Popu);//初始化種群
    for(int i=0;i<Popu.size();i++)
    {
        Cost(Popu[i]);
    }
    int k=0;//第k代數
    for(;k<G;k++)
    {
        //選擇(精英選擇1+輪盤選擇)
        Choose(Popu);
        //交叉
        Cross(Popu);
        //變異
        Variation(Popu);
        //cout<<k<<":"<<(Popu[8]).cost<<endl;//第k次迭代最好代價
    }
    sort(Popu.begin(),Popu.end(),Cmp);
    //Save(Popu[0]);
    //copy((Popu[0]).path.begin(),(Popu[0]).path.end(),ostream_iterator<int>(cout," "));cout<<endl;//輸出answer
    cout<<(Popu[0]).cost<<endl;
    return  (Popu[0]).cost;
}
/*************************************************************
//上面是遺傳演算法部分
//下面是實驗部分
//產生7個問題規模的檔案
//儲存實驗結果
**************************************************************/
void GenerateCity(int cityNum)
{

    fstream fileWrite;
    char fileName[20];
    sprintf(fileName,"city_coor_%d.txt",cityNum);

    fileWrite.open(fileName,ios::out|ios::app);
    //fileWrite<<"城市個數:"<<CITYNUM<<";種群規模:"<<M<<";交叉概率:"<<PC<<";變異概率:"<<PM<<";代數:"<<G<<endl;
    //fileWrite<<"最小代價:"<<answer.cost<<endl;
    for(int i=0;i<cityNum;i++)
    {
        //fileWrite<<citys[answer.path[i]].name<<"->";
        citys[i].x=rand()%RANGE;
        citys[i].y=rand()%RANGE;
        fileWrite<<i<<"\t"<<citys[i].x<<"\t"<<citys[i].y<<endl;
    }
    fileWrite.close();

}
void PopulationTest(int cityNums[])
{
    for(int i=6;i<7;i++)
    {
        InitGraph(cityNums[i]);
        CITYNUM=cityNums[i];
        fstream fileWrite;
        char fileName[30];
        sprintf(fileName,"out_city_%d_popu.txt",CITYNUM);
        fileWrite.open(fileName,ios::out|ios::app);
        fileWrite<<"比例"<<'\t'<<"最好"<<'\t'<<"均值"<<'\t'<<"最差"<<endl;
        for(int j=1;j<=9;j++)
        {
            M=CITYNUM*j/10;//  1/10  2/10   3/10 ... 9/10
            PC=0.8;
            PM=0.2;
            G=500*CITYNUM;
            double sum=0,avgCost=0,minCost=MAX,maxCost=0;

            for(int k=0;k<10;k++)
            {
                double temp=GA();
                sum+=temp;
                if(temp<minCost)minCost=temp;
                if(temp>maxCost)maxCost=temp;
            }
            fileWrite<<j<<"0%"<<'\t'<<minCost<<'\t'<<sum/10<<'\t'<<maxCost<<endl;
        }
        fileWrite.close();
    }
}
void CrossVarTest(int cityNums[],double crossP[],double varP[])
{
    for(int i=0;i<7;i++)
    {
        InitGraph(cityNums[i]);
        CITYNUM=cityNums[i];

        for(int j=0;j<11;j++)
        {
            if(CITYNUM<100)M=CITYNUM*9/10;
            else if(CITYNUM>=100&&CITYNUM<200)M=CITYNUM*4/10;
            else M=40;

            PC=crossP[j];
            G=200*CITYNUM;

            fstream fileWrite;
            char fileName[50];
            sprintf(fileName,"out_%d_cross_%.2f.txt",CITYNUM,PC);
            fileWrite.open(fileName,ios::out|ios::app);
            fileWrite<<"變異"<<'\t'<<"最好"<<'\t'<<"均值"<<'\t'<<"最差"<<endl;

            for(int u=0;u<12;u++)
            {
                PM=varP[u];
                double sum=0,avgCost=0,minCost=MAX,maxCost=0;
                for(int k=0;k<10;k++)
                {
                    double temp=GA();
                    sum+=temp;
                    if(temp<minCost)minCost=temp;
                    if(temp>maxCost)maxCost=temp;
                }
                fileWrite<<PM<<'\t'<<minCost<<'\t'<<sum/10<<'\t'<<maxCost<<endl;
            }
            fileWrite.close();
        }
    }
}
int main()
{
    srand ( unsigned (time(NULL) ) );
    int cityNums[7]={10,20,30,50,70,100,200};
    double crossP[11]={0.01,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,0.99};
    double varP[12]={0.001,0.01,0.05,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9};
    //for(int i=0;i<7;i++)
    //{
        //GenerateCity(cityNums[i]);
    //}

    //PopulationTest(cityNums);
    //CrossVarTest(cityNums,crossP,varP);
    //srand ( unsigned (time(NULL) ) );
    //G=100000;
    //InitGraph(200);
    //GA();
    return 0;
}