解決旅行商問題的方法

旅行商問題

定義:

旅行推銷員問題(英語:Travelling salesman problem, TSP)是這樣一個問題:給定一系列城市和每對城市之間的距離,求解訪問每一座城市一次並回到起始城市的最短迴路。它是組合優化中的一個NP難問題,在運籌學和理論計算機科學中非常重要。
最早的旅行商問題的數學規劃是由Dantzig(1959)等人提出,並且是在最優化領域中進行了深入研究。許多優化方法都用它作爲一個測試基準。儘管問題在計算上很困難,但已經有了大量的啓發式算法和精確方法來求解數量上萬的實例,並且能將誤差控制在1%內。

簡介:

旅行商問題(TravelingSalesmanProblem,TSP)是一個經典的組合優化問題。經典的TSP可以描述爲:一個商品推銷員要去若干個城市推銷商品,該推銷員從一個城市出發,需要經過所有城市後,回到出發地。應如何選擇行進路線,以使總的行程最短。從圖論的角度來看,該問題實質是在一個帶權完全無向圖中,找一個權值最小的Hamilton迴路。由於該問題的可行解是所有頂點的全排列,隨着頂點數的增加,會產生組合爆炸,它是一個NP完全問題。由於其在交通運輸、電路板線路設計以及物流配送等領域內有着廣泛的應用,國內外學者對其進行了大量的研究。早期的研究者使用精確算法求解該問題,常用的方法包括:分枝定界法、線性規劃法、動態規劃法等。但是,隨着問題規模的增大,精確算法將變得無能爲力,因此,在後來的研究中,國內外學者重點使用近似算法或啓發式算法,主要有遺傳算法、模擬退火法、蟻羣算法、禁忌搜索算法、貪婪算法和神經網絡等。

智能優化算法解決旅行商問題

遺傳算法

TSP問題基本概念

TSP問題即旅行商問題(TravelingSalesperson Problem)。該問題給定n個城市和兩兩城市之間的距離,要求確定一條經過各城市當且僅當一次的最短路線。其圖論描述爲:給定圖G=(V, A),其中V爲頂點集,A爲各頂點相互連接組成的邊集,已知各頂點間的連接距離,要求確定一條長度最短的Hamilton迴路,即遍歷所有頂點當且僅當一次的最短迴路。

遺傳算法的基本原理

遺傳算法是一類隨機優化算法,但它不是簡單的隨機比較搜索,而是通過對染色體的評價和對染色體中基因的作用,有效地利用已有信息來指導搜索有希望改善優化質量的狀態。

標準遺傳算法主要步驟可描述如下:

① 隨機產生一組初始個體構成初始種羣。

② 計算每一個體的適配值(fitnessvalue,也稱爲適應度)。適應度值是對染色體(個體)進行評價的一種指標,是GA進行優化所用的主要信息,它與個體的目標值存在一種對應關係。

③ 判斷算法收斂準則是否滿足,若滿足,則輸出搜索結果;否則執行以下步驟。

④ 根據適應度值大小以一定方式執行復制操作(也稱爲選擇操作)。

⑤ 按交叉概率pc執行交叉操作。

⑥ 按變異概率pm執行變異操作。

⑦ 返回步驟②。

標準遺傳算法流程圖下圖所示。

1、設計算法的編碼方式

路徑編碼是描述TSP解的最常用的一種策略。所謂路徑編碼,即直接採用城市在路徑中的位置來構造用於優化的狀態。

例如:設九城市TSP問題的路徑爲5-4-1-7-9-8-6-2-3,

對應的路徑編碼爲:(5 4 1 7 9 8 6 23)。

這種編碼形式自然直觀,易於加入啓發式信息,也有利於優化操作的設計。

2、設計遺傳算法的適應度函數

對個體i,計算與路徑編碼相對應的距離,設爲di。顯然距離值di越大,適應度值應越小。因此,適應度函數可定義爲f=1/di。

3、設計遺傳算法的選擇操作

選擇是用來確定交叉個體,以及被選個體將產生多少個子代個體。它是基於適應度值計算基礎上進行的。在被選集中,每個個體都有一個選擇概率,這個概率由種羣中個體的適應度及其分佈決定。若某個個體i,其適應度爲fi,則其被選取的概率表示爲:。

4、設計遺傳算法的交叉操作

在選擇操作的基礎上,根據一定的概率(稱爲交叉概率)進行交叉操作。交叉的目的是爲了能夠在下一代產生新的個體,它是遺傳算法獲取新的優良個體的最重要的手段。交叉操作中,把兩個父個體的部分結構進行替換重組,生成新個體。根據個體編碼方法的不同可以有不同的算法。

TSP問題中,交叉操作可設計如下:

遺傳算法中並不是所有被選擇的個體,都要進行交叉操作。交叉概率用於控制交叉操作的頻率。概率太大時,種羣中串的更新很快,使高適應度值的個體很快被破壞掉。概率太小時,交叉操作很少進行,使搜索停滯不前。

5、設計遺傳算法的變異操作

同交叉操作一樣,並不是所有被選擇的個體,都要進行變異操作。變異概率是加大種羣多樣性的重要因素,但是概率太小就很難產生新個體,概率太大會使GA成爲隨機搜索。基於二進制編碼的GA中,通常一個較低的變異率足以防止整個羣體中任一位置的基因一直保持不變。

TSP問題中,變異操作可設計如下:

6、編寫基於遺傳算法的TSP問題求解程序

遺傳算法的優點和缺點

遺傳算法的優點:

與問題領域無關切快速隨機的搜索能力;

搜索從羣體出發,具有潛在的並行性,可以進行多個個體的同時比較;

搜索使用評價函數啓發,過程簡單;

使用概率機制進行迭代,具有隨機性;

具有可擴展性,容易與其他算法結合。

遺傳算法的缺點:

遺傳算法的編程實現比較複雜,首先需要對問題進行編碼,找到最優解之後還需要對問題進行解碼;

另外三個算子的實現也有許多參數,如交叉率和變異率,並且這些參數的選擇嚴重影響解的品質,而目前這些參數的選擇大部分是依靠經驗;

算法對初始種羣的選擇有一定的依賴性,能夠結合一些啓發算法進行改進;

算法的並行機制的潛在能力沒有得到充分的利用,這也是當前遺傳算法的一個研究熱點方向。

現在的工作中,遺傳算法(1972年提出)已經不能很好的解決大規模計算量問題,它很容易陷入“早熟”。常用混合遺傳算法,合作型協同進化算法等來替代,這些算法都是GA的衍生算法。

遺傳算法具有良好的全局搜索能力,可以快速地將解空間中的全體解搜索出,而不會陷入局部最優解的快速下降陷阱;並且利用它的內在並行性,可以方便地進行分佈式計算,加快求解速度。但是遺傳算法的局部搜索能力較差,導致單純的遺傳算法比較費時,在進化後期搜索效率較低。在實際應用中,遺傳算法容易產生早熟收斂的問題。採用何種選擇方法既要使優良個體得以保留,又要維持羣體的多樣性,一直是遺傳算法中較難解決的問題。

模擬退火算法雖具有擺脫局部最優解的能力,能夠以隨機搜索技術從概率的意義上找出目標函數的全局最小點。但是,由於模擬退火算法對整個搜索空間的狀況瞭解不多,不便於使搜索過程進入最有希望的搜索區域,使得模擬退火算法的運算效率不高。模擬退火算法對參數(如初始溫度)的依賴性較強,且進化速度慢。

對我而言,難點是參數的調節和適應度函數的選擇,由於缺乏經驗,剛開始參數調節的效果並不太好,但是在不斷地改進中,能夠獲得較好的參數。

從運行結果發現遺傳算法的缺點:進化停滯。往往前幾代就找到了局部最優解,也就是早熟現象。發現也有很多人對這方面最了改進。比如調整選擇算法,在最初幾代降低優秀個體差異帶來的選擇概率,後面增加優秀個體被選擇的概率。還有變異時簡單的單點變異會造成等位基因的缺失,所以採用兩條染色體求同或和異或保證等位基因的多樣性。這些都能降低收斂速度。

#include<iostream>
#include<fstream>
#include<cmath>
#include<algorithm>
#include<ctime>
using namespace std;


const int city_num = 30; //城市個數


#define POPULATION_NUM 2000 //種羣數量
#define CROSSOVER_RATE 0.65 //交叉概率
#define ABERRATION_RAET 0.05 //變異概率
#define ITERATIONS 30 //若連續ITERATIONS次解無改進,則退出遺傳算法


struct OneRoad{
	int path[city_num];//路徑信息
	double length; //總路徑長度
	double fit; //適應度
	double sum_fit; //累計適應度概率
};//一條路徑的信息


OneRoad* road;//定義一個種羣


const double city_coord[city_num][2] = {
	{ 41, 94 }, { 37, 84 }, { 53, 67 }, { 25, 62 }, { 7, 64 }, { 2, 99 }, { 68, 58 }, { 71, 44 },
	{ 54, 62 }, { 83, 69 }, { 64, 60 }, { 18, 54 }, { 22, 60 }, { 83, 46 }, { 91, 38 }, { 25, 38 },
	{ 24, 42 }, { 58, 69 }, { 71, 71 }, { 74, 78 }, { 87, 76 }, { 18, 40 }, { 13, 40 }, { 82, 7 },
	{ 62, 32 }, { 58, 35 }, { 45, 21 }, { 41, 26 }, { 44, 35 }, { 4, 50 } };//城市座標


//void ReadFile(); //讀取文件
void Init(); //初始化種羣
void Evluate(); //計算適應度
void Select(); //選擇
void CrossOver(); //交叉函數
void Mutation(); //變異函數


int main(){
	//ReadFile();
	road = (OneRoad*)malloc(sizeof(OneRoad)*POPULATION_NUM);
	Init();
	OneRoad allbest, best;
	allbest.fit = 0;
	best.fit = 0;
	clock_t start, end;
	int k = 0;
	start = clock();
	Evluate();
	while (k < ITERATIONS){		
		Select();
		Evluate();
		CrossOver();
		Evluate();
		Mutation();
		Evluate();
		for (int i = 0; i < POPULATION_NUM; i++){
			if (best.fit < road[i].fit){
				best = road[i];
			}
		}
		if (allbest.fit < best.fit){
			allbest = best;
			k = 0;
		}
		else{
			k++;
		}
	}
	end = clock();
	cout << "最短距離是:" << allbest.length << endl;
	cout << "最短路徑是:" << endl;
	cout << allbest.path[0] + 1;
	for (int i = 1; i < city_num; i++)
		cout << "->" << allbest.path[i] + 1;
	cout << "->" << allbest.path[0] + 1;
	cout << endl;
	cout << "運行時間是:" << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl;
	free(road);


	system("pause");
	return 0;
}


/*void ReadFile(){
	ifstream read_in;
	read_in.open("F:\\課程\\智能信息處理\\我的代碼\\GA\\GA\\TSP30.txt", ios::in);
	if (!read_in.is_open()){
		cout << "the file is opened error!" << endl;
		return;
	}
	for (int i = 0; i < city_num; i++){
		read_in >> city_coord[i][0] >> city_coord[i][1];
	}
	read_in.close();
}*/


void Init(){
	int i, j;
	int t_city;
	for (i = 0; i < POPULATION_NUM; i++){
		road[i].path[0] = 0;
		bool flag[city_num] = {};
		for (j = 1; j < city_num; j++){
			t_city = rand() % city_num;
			while (flag[t_city] || t_city == 0){
				t_city = rand() % city_num;
			}
			flag[t_city] = true;
			road[i].path[j] = t_city;
		}
	}
}


void Evluate(){
	int a = 0, b = 0;
	double sum = 0;
	double x1 = 0, x2 = 0, y1 = 0, y2 = 0;	
	for (int i = 0; i < POPULATION_NUM; i++){			
		road[i].length = 0;
		for (int j = 0; j < city_num - 1; j++){
			a = road[i].path[j];
			b = road[i].path[j + 1];
			x1 = city_coord[a][0];
			y1 = city_coord[a][1];
			x2 = city_coord[b][0];
			y2 = city_coord[b][1];
			road[i].length += sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
		}
		road[i].length += sqrt((city_coord[0][0] - city_coord[city_num - 1][0])*(city_coord[0][0] - city_coord[city_num - 1][0])
			+ (city_coord[0][1] - city_coord[city_num - 1][1])*(city_coord[0][1] - city_coord[city_num - 1][1]));	
	}
	for (int i = 0; i < POPULATION_NUM; i++)
	{
		road[i].fit = 1.0 / road[i].length*1.0 / road[i].length;
		sum += road[i].fit;
	}
	road[0].sum_fit = road[0].fit / sum;
	for (int i = 1; i < POPULATION_NUM; i++)
		road[i].sum_fit = road[i].fit / sum + road[i - 1].sum_fit;
}


void Select(){
	OneRoad* t = (OneRoad*)malloc(sizeof(OneRoad)*POPULATION_NUM);
	srand((unsigned)time(NULL));
	for (int i = 0; i < POPULATION_NUM; i++)
		t[i] = road[i];
	for (int i = 0; i < POPULATION_NUM; i++){
		double sum = (rand() % 10000) / 9999.0;
		for (int j = 0; j < POPULATION_NUM; j++){
			if (road[j].sum_fit > sum){
				t[i] = road[j];
				break;
			}
		}
	}
	for (int i = 0; i < POPULATION_NUM; i++){
		road[i] = t[i];
	}
	free(t);
}


void CrossOver(){
	OneRoad* t = (OneRoad*)malloc(sizeof(OneRoad)*POPULATION_NUM);
	srand((unsigned)time(NULL));
	for (int i = 0; i < POPULATION_NUM; i++)
		t[i] = road[i];
	for (int i = 0; i < POPULATION_NUM - 1; i = i + 2)
	{
		bool flag[city_num] = {};
		double p = (rand() % 10000) / 9999.0;
		if (p < CROSSOVER_RATE){
			int a = rand() % city_num;
			int b = rand() % city_num;
			while (a == 0 || b == 0){
				a = rand() % city_num;
				b = rand() % city_num;
			}
			if (a > b)swap(a, b);
			for (int j = a; j <= b; j++){
				t[i].path[j] = road[i].path[j];
				flag[road[i].path[j]] = true;
			}
			int k = 1;
			if (a == 1)
				k = b + 1;
			for (int j = 1; j < city_num; j++){
				if (!flag[road[i + 1].path[j]]){
					t[i].path[k] = road[i + 1].path[j];
					k++;
					if (k == a)
						k = b + 1;
				}
			}
		}
		else{
			for (int j = 0; j < city_num; j++){
				t[i].path[j] = road[i + 1].path[j];
			}
		}
	}
	for (int i = 0; i < POPULATION_NUM - 1; i = i + 2){
		for (int j = 0; j < city_num; j++){
			road[i].path[j] = t[i].path[j];
		}
	}
	free(t);
}


void Mutation(){
	int x1 = 0, x2 = 0;
	srand((unsigned)time(NULL));
	for (int i = 0; i < POPULATION_NUM; i++){
		double p = (rand() % 10000) / 9999.0;
		if (p < ABERRATION_RAET){
			x1 = rand() % city_num;
			x2 = rand() % city_num;
			while (x1 == 0 || x2 == 0){
				x1 = rand() % city_num;
				x2 = rand() % city_num;
			}
			swap(road[i].path[x1], road[i].path[x2]);
		}
	}
}

參考博客: link.

其它算法

求解中國旅行商問題的幾種智能算法

參考文獻:

參考文獻:link.

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