旅行商問題的描述
試想一下,一個業務員因工作需要必須訪問多個城市。他的目標是每個城市只訪問一次,並且盡可能地縮短旅行的距離,最終返回到他開始旅行的地點,這就是旅行商問題的主要思想。
在一幅圖中,訪問每個頂點一次,並最終返回起始頂點,這個訪問的軌跡稱為哈密頓圈。要解決旅行商問題,需要用圖G=(V,E)作為模型,尋找圖中最短的哈密頓圈。G是一個完整的、無方向的帶權圖,其中V代表將要訪問的頂點的集合,E為連接這些頂點的邊的集合。E中每條邊的權值由頂點之間的距離決定。由於G中一個完整的、無方向的圖,因此E包含V(V-1)/2條邊。
事實上,旅行商問題是一種特殊的非多項式時間問題,稱為NP完全問題。NP完全問題是指那些多項式時間算法未知,倘沒有證據證明沒有解決的可能性的問題。考慮到這種思想,通常用一種近似算法來解決旅行商問題。
最近鄰點法的應用
一種近似的計算旅行商路線的方法就是使用最近鄰點法。
其運算過程如下:從一條僅包含起始頂點的路線開始,將此頂點塗黑。其他頂點為白色,在將其他頂點加入此路線中后,再將相應頂點塗黑。接着,對於每個不在路線中的頂點v,要為最后加入路線的頂點u與v之間的邊計算權值。在旅行商問題中,u與v之間邊的權值就是u到v之間的距離。這個距離可以用每個頂點的坐標計算得到。兩個點(x1,y1)與(x2,y2)之間距離的計算公式如下:
r = √(x2 - x1)2 + (y2 - y1)2 (注意是求和的平方根)
利用這個公式,選擇最接近u的頂點,將其塗黑,同時將其加入路線中。接着重復這個過程,直到所有的頂點都塗成黑色。此時再將起始頂點加入路線中,從而形成一個完整的回路。
下圖展示了使用最近鄰點法來解決旅行商問題的方法。通常,在為旅行商問題構造一個圖時,每個頂點之間相連的邊不會顯示表示出來,因為這種表示會讓圖不清晰了,也沒有必要。在圖中,每個頂點旁邊都顯示其坐標值,虛線表示在此階段需要比較距離的邊。顏色最深的是要加入路線中的邊。通過最近鄰點法獲取的路線長度為15.95。而最優路線的長度為14.71,比最近鄰點法得到的長度少8%。
最近鄰點法有一些有趣的特性,它類似廣度優先搜索算法,因為在往圖更深一層探尋之前,它需要掃描與路線中最后頂點相鄰的所有頂點。它還應用了貪心算法,因為每次它將一個頂點加入路線時,它都選擇當前最優的頂點。遺憾的是,在一個結點加入當前最鄰近的點可能會給接下來的路線帶來負面影響。然而,它通常會返回一條2倍於最優路線的路線,但在許多情況下,結果會比這要好。當計算路線時,改善算法的方法是存在的,一種改善的方法就是使用交互式啟發法(在此不再展開)。
旅行商問題的接口定義
tsp
int tsp (List *vertices,const TspVertex *start, List *tour, int (*match)(const void *key1, const void key2))
返回值:如果計算近似旅行商路線成功,返回0;否則,返回-1。
描述:為存儲在vertices中的頂點計算一條近似旅行商的路線。路線的起始點為start指定的頂點。此操作會改變vertices,所以如果有必要,在調用此操作之前需要先備份vertices。
vertices中每個元素都必須是TspVertex類型。用TspVertex結構體的成員data來保存與頂點相關的數據,例如頂點標識符。用其成員x和y來指定頂點的坐標。match函數判斷兩個頂點是否匹配,它僅用來比較TspVertex結構體的data成員。計算得到的路線存儲在tour中,tour是TspVertex結構體列表。tour中保存的頂點會按照路線中頂點的順序排放。tour中元素指向vertices中實際的頂點,所以只要能夠訪問tour,函數調用者就必須保證vertices的內存空間有效。如果不再使用tour,那么可以調用list_destroy來銷毀tour。
復雜度:O(V2),其中V是路線中要訪問的頂點的個數。
旅行商問題的實現與分析
解決旅行商問題,首先從一個由頂點列表表示的圖開始。用這種方式表示的圖,其每條邊都是隱式的。列表中的每個頂點都是一個TspVertex結構體。此結構體包含4個成員:data用來保存頂點有送的數據;x和y表示頂點的坐標;color為頂點的色值。
tsp操作首先將所有的頂點塗成白色(除起始頂點外,起始頂點會塗黑),且立刻加入線路中。同時,記錄起始頂點的坐標值,這樣在主循環的首次迭代過程中,就可以計算起始頂點與其他頂點之間的距離。在主循環中,將所有剩余頂點加入路線中。在每次迭代過程中,尋找離最后加入的頂點最近的白色頂點。每次加入一個頂點,就為下次迭代記錄它的坐標,同時將其塗成黑色。在循環結束后,再次將起始頂點加入路線中,以形成一條閉合路徑。
tsp的時間復雜度是O(V2),其中V是路徑中要訪問的頂點個數。這是因為,對於主循環中每V-1次迭代,都需要搜索顏色為白色而且需要為其計算距離的頂點。注意O(V2)的復雜度對於計算一條最優路徑的復雜度O(V!)來說已經是很大的改進了。
頭文件:旅行商問題頭文件
/*graphalg.h*/ #ifndef GRAPHALG_H #define GRAPHALG_H #include "graph.h" #include "list.h" /*定義旅行商問題中結點的數據結構*/ typedef struct TspVertex_ { void *data; double x,y; VertexColor color; }TspVertex; /*函數接口*/ int tsp(List *vertexs, const TspVertex *start, List *tour, int (match*)(const void *key1, const void *key2)); #endif // GRAPHALG_H
示例16-5: 解決旅行商問題的實現
/*tsp.c*/ #include <float.h> #include <math.h> #include <stdlib.h> #include "graph.h" #include "graphalg.h" #include "list.h" int tsp(List *vertices, const TspVertex *start, List *tour, int (*match)(const void *key1, const void *key2)) { TspVertex *tsp_vertex, *tsp_start, *selection; ListElmt *element; double minimum, distance, x, y; int found, i; /*初始化列表tour(存儲計算得到的路線)*/ list_init(tour,NULL); /*初始化圖中的所有頂點*/ found = 0; for(element = list_head(vertices); element != NULL; element = list_next(element)) { tsp_vertex = list_data(element); if(match(tsp_vertex,start)) { /*將起始頂點加入到路線列表中*/ if(list_ins_next(tour,list_tail(tour),tsp_vertex) != 0) { list_destroy(tour); return -1; } /*保存起始頂點以及它的坐標值*/ tsp_start = tsp_vertex; x = tsp_vertex->x; y = tsp_vertex->y; /*將起始頂點塗成黑色*/ tsp_vertex->color = black; found = 1; } else { /*除此之外的其他頂點都塗成白色*/ tsp_vertex->color = white; } } /*如果沒有找到起始頂點,程序返回*/ if(!found) { list_destroy(tour); return -1; } /*使用最近鄰點法計算路線*/ while(i < list_size(vertices)-1) { /*從白色頂點中選擇離當前頂點最近的頂點*/ minmum = DBL_MAX; for(element = list_head(vertices); element != NULL; element = list_next(element)) { tsp_vertex = list_data(element); if(tsp_vertex->color == white) { distance = sqrt(pow(tsp_vertex->x-x,2.0) + pow(tsp_vertex->y-y, 2.0)); if(distance < minimum) { minimum = distance; selection = tsp_vertex; } } } /*保存符合要求頂點的坐標*/ x = selection->x; y = selection->y; /*將被選中的頂點塗成黑色*/ selection->color = black; /*將選中頂點插入到路線鏈表中*/ if(list_ins_next(tour, list_tail(tour),selection) != 0) { list_destroy(tour); return -1; } /*准備選取下一個頂點*/ i++; } /*最后再將起始頂點插入到計算路線中,形成閉合路線*/ if(list_ins_next(tour,list_tail(tour),tsp_start) != 0) { list_destroy(tour); return -1; } return 0; }