標籤:
Travelling Salesman Problem
旅行商問題,即TSP問題(Travelling Salesman Problem)又譯為旅行推銷員問題、貨郎擔問題,是數學領域中著名問題之一。假設有一個旅行商人要拜訪n個城市,他必須選擇所要走的路徑,路徑的限制是每個城市只能拜訪一次,而且最後要回到原來出發的城市。路徑的選擇目標是要求得的路徑路程為所有路徑之中的最小值。
旅行商問題是圖論中最著名的問題之一,即“已給一個n個點的完全圖,每條邊都有一個長度,求總長度最短的經過每個頂點正好一次的封閉迴路”。該問題通常被認為是一個NP完全問題。時間複雜度為O(n!)。因此,通常n的值不是很大。
因此我們如何求其近似解(非多項式時間演算法)呢?這裡使用動態規劃
- 假定我們從城市1出發,經過了一些地方,併到達了城市j。毋庸置疑,我們需要記錄的資訊有當前的城市j。同時我們還需要記錄已經走過的城市的集合。同理,使用S記錄未走過的城市的集合也可以的,且運算方便。
- 於是我們可以得出狀態轉移方程 go(S,init)=min{go(S?i,i)+dp[i][init]}?s∈S go(s,init)表示從init點開始,要經過s集合中的所有點的距離
- 因為是NP問題,所以時間複雜度通常比較大。使用dis[s][init]用來去重,初始化為-1,如果已經計算過init—>S(遞迴形式),則直接返回即可
例: Sicily1000. Traveling Salesman Problem
題目大意:有編號1到N的N個城市,問從1號城市出發,遍曆完所有的城市並最後停留在N號城市的最短路徑長度。N<=20
那麼如何有效地儲存當前還未走過的城市集合S呢?使用一個整形即可。每個整形可以表示32位,即32個城市,用1代表當前未去,0代表當前已經訪問過的點。因N<=20,所以S<=1048576.
代碼如下:*
#include<iostream>#include<cmath> #include<iomanip>using namespace std;int s;int N;//點的個數 int init_point;double x[20];double y[20];double dp[20][20];//兩個城市的距離 double dis[1048577][20];//2^20=1048576 表示出發點到S集合是否已經訪問過 double go(int s,int init){ if(dis[s][init]!=-1) return dis[s][init];//去重 if(s==(1<<(N-1))) return dp[N-1][init];//只有最後一個點返回 double minlen=100000; for(int i=0;i<N-1;i++)//只能在1到n-2點中尋找 { if(s&(1<<i))//如果i點在s中且不為發出點 { if(go(s&(~(1<<i)),i)+dp[i][init]<minlen) minlen=go(s&(~(1<<i)),i)+dp[i][init]; } } return dis[s][init]=minlen;}int main(){ int T; cin>>T; while(T--)//測試範例數 { cin>>N; for(int i=0;i<N;i++) cin>>x[i]>>y[i];//讀入城市的座標 for(int i=0;i<N;i++) for(int j=0;j<N;j++) { dp[i][j]=sqrt(pow((x[i]-x[j]),2)+pow((y[i]-y[j]),2)); //計算兩個城市的距離 } for(int i=0;i<pow(2,N);i++) for(int j=0;j<N;j++) dis[i][j]=-1;//去重數組初始化 init_point=0; s=0; for(int i=1;i<N;i++) s=s|(1<<i);//從1開始,保證初始點沒有在S裡面 double distance=go(s,init_point); cout<<fixed<<setprecision(2)<<distance<<endl; } }
動態規劃經典問題--TSP問題