試想一下,一個業務員因工做須要必須訪問多個城市。他的目標是每一個城市只訪問一次,而且儘量地縮短旅行的距離,最終返回到他開始旅行的地點,這就是旅行商問題的主要思想。算法
在一幅圖中,訪問每一個頂點一次,並最終返回起始頂點,這個訪問的軌跡稱爲哈密頓圈。要解決旅行商問題,須要用圖G=(V,E)做爲模型,尋找圖中最短的哈密頓圈。G是一個完整的、無方向的帶權圖,其中V表明將要訪問的頂點的集合,E爲鏈接這些頂點的邊的集合。E中每條邊的權值由頂點之間的距離決定。因爲G中一個完整的、無方向的圖,所以E包含V(V-1)/2條邊。數據結構
事實上,旅行商問題是一種特殊的非多項式時間問題,稱爲NP徹底問題。NP徹底問題是指那些多項式時間算法未知,倘沒有證據證實沒有解決的可能性的問題。考慮到這種思想,一般用一種近似算法來解決旅行商問題。函數
一種近似的計算旅行商路線的方法就是使用最近鄰點法。spa
其運算過程以下:從一條僅包含起始頂點的路線開始,將此頂點塗黑。其餘頂點爲白色,在將其餘頂點加入此路線中後,再將相應頂點塗黑。接着,對於每一個不在路線中的頂點v,要爲最後加入路線的頂點u與v之間的邊計算權值。在旅行商問題中,u與v之間邊的權值就是u到v之間的距離。這個距離能夠用每一個頂點的座標計算獲得。兩個點(x1,y1)與(x2,y2)之間距離的計算公式以下:3d
r = √(x2 - x1)2 + (y2 - y1)2 (注意是求和的平方根)code
利用這個公式,選擇最接近u的頂點,將其塗黑,同時將其加入路線中。接着重複這個過程,直到全部的頂點都塗成黑色。此時再將起始頂點加入路線中,從而造成一個完整的迴路。blog
下圖展現了使用最近鄰點法來解決旅行商問題的方法。一般,在爲旅行商問題構造一個圖時,每一個頂點之間相連的邊不會顯示錶示出來,由於這種表示會讓圖不清晰了,也沒有必要。在圖中,每一個頂點旁邊都顯示其座標值,虛線表示在此階段須要比較距離的邊。顏色最深的是要加入路線中的邊。經過最近鄰點法獲取的路線長度爲15.95。而最優路線的長度爲14.71,比最近鄰點法獲得的長度少8%。接口
最近鄰點法有一些有趣的特性,它相似廣度優先搜索算法,由於在往圖更深一層探尋以前,它須要掃描與路線中最後頂點相鄰的全部頂點。它還應用了貪心算法,由於每次它將一個頂點加入路線時,它都選擇當前最優的頂點。遺憾的是,在一個結點加入當前最鄰近的點可能會給接下來的路線帶來負面影響。然而,它一般會返回一條2倍於最優路線的路線,但在許多狀況下,結果會比這要好。當計算路線時,改善算法的方法是存在的,一種改善的方法就是使用交互式啓發法(在此再也不展開)。內存
tspelement
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; }