蠻力法
蠻力法也稱窮舉法或枚舉法,是一種簡單直接地解決問題的方法,常常直接基於問題的描述,所以蠻力法也是最容易應用的方法。蠻力法所依賴的基本技術是遍歷,也稱掃描,即采用一定的策略依次理待求解問題的所有元素,從而找出問題的解。依次處理所有元素是蠻力法的關鍵,為了避免陷人重復試探,應保證處理過的元素不再被處理。
TSP 問題
TSP 問題(Traveling Salesman Problem)又譯為旅行推銷員問題、貨郎擔問題,是數學領域中著名問題之一。假設有一個旅行商人要拜訪 n 個城市,他必須選擇所要走的路徑,路徑的限制是每個城市只能拜訪一次,而且最后要回到原來出發的城市。路徑的選擇目標是要求得的路徑路程為所有路徑之中的最小值。——百度百科
使用蠻力法求解 TSP 問題的思想是,通過窮舉的方式把所有可能的路徑找出來,然后對每一條路徑都計算開銷,最終找出開銷最小的路徑。例如對於如圖 4 個城市的拓撲,使用蠻力法求解的過程如表格所示。
序號 | 路徑 | 路徑長度 | 是否最短 |
---|---|---|---|
1 | a->b->c->d->a | 18 | 否 |
2 | a->b->d->c->a | 11 | 是 |
3 | a->c->b->d->a | 23 | 否 |
4 | a->c->d->b->a | 11 | 是 |
5 | a->d->b->c->a | 23 | 否 |
6 | a->d->c->b->a | 18 | 否 |
當城市規模增大時,存在的路徑數會呈現指數型增長,例如 11 個城市的拓撲圖如下所示。
實驗程序編寫
圖結構體定義
TSP 是個 NP 完全問題,我們需要圖結構來進行存儲。我選擇鄰接矩陣存儲城市拓撲圖,定義的圖結構體如下。
typedef struct //圖的定義
{
int edges[MAXV][MAXV]; //鄰接矩陣
int n; //頂點數
} MGraph;
城市拓撲的建立
接下來就需要把城市拓撲存儲在鄰接矩陣中,因為城市拓撲是完全圖,因此我們需要存儲所有城市之間的距離。
MGraph CreateMGraph(int num) //建圖
{
MGraph topography;
for (int i = 1; i <= num; i++)
{
for (int j = 1; j <= num; j++)
{
topography.edges[i][j] = 0;
}
}
for (int i = 1; i <= num; i++)
{
for (int j = i + 1; j <= num; j++)
{
printf("城市%d和城市%d之間的距離為:",i,j);
cin >> topography.edges[i][j];
topography.edges[j][i] = topography.edges[i][j];
}
}
topography.n = num;
return topography;
}
DFS
想要獲取最短的路線,使用蠻力法進行分析時需要先獲取所有的路徑。DFS 可以獲取所有的路徑,編寫的代碼如下。注意當獲取一條新路徑時,需要先把該路徑拷貝到下一個路徑,因為遞歸實現的 DFS 無法返回上一層遞歸執行填充操作。這么做是可行的,因為相鄰路徑不需要回溯的路線是一樣的,而回溯的部分會直接覆蓋原來的路線。
void DFS(int new_point, int cities_visited, int &path_index) //深度遍歷
{
count++;
if (cities_visited == topography.n) //所有城市都走一遍
{
path[path_index][cities_visited] = new_point;
path[path_index][cities_visited + 1] = start_point; //回到出發點
for(int i = 1; i <= topography.n; i++)
{
path[path_index + 1][i] = path[path_index][i]; //下一條路徑拷貝上一條
}
path_index++;
}
else
{
for (int i = 1; i <= topography.n; i++)
{
if (visited[i] == 0)
{
visited[i] = 1;
path[path_index][cities_visited] = new_point;
DFS(i, cities_visited + 1, path_index);
visited[i] = 0; //回溯到上一城市
}
}
}
return;
}
主函數
接下來要計算所有路徑的長度,並且得出最短的路線。同時我們也需要確定 DFS 執行了多少次,方便我們分析時間復雜度。
int main()
{
int cities_num = 0; //城市數量
int path_num = 1; //路徑數
int cities_visited = 1; //已訪問城市數
int path_index = 1; //已獲取的路徑數
int min_path = 0;
int min_sum = 9999999;
int sum;
cout << "城市數量為:";
cin >> cities_num;
//建圖
topography = CreateMGraph(cities_num);
for(int i = cities_num - 1; i > 1; i--)
{
path_num *= i;
}
//初始化訪問狀態
for(int i = 1; i <= topography.n; i++)
{
visited[i] = 0;
}
//出發
cout << "從哪個城市出發:";
cin >> start_point;
visited[start_point] = 1;
//獲取所有路徑
DFS(start_point, cities_visited, path_index);
//得出最短路徑
ofstream outfile;
outfile.open("11.txt");
for (int i = 1; i < path_index; i++)
{
sum = 0;
outfile << "路徑" << i << ":";
for (int j = 1; j <= cities_num; j++)
{
sum += topography.edges[ path[i][j] ][ path[i][j + 1] ];
}
if(sum < min_sum)
{
min_sum = sum;
min_path = i;
}
}
cout << "\n最短路徑為路徑" << min_path << ":";
for (int j = 1; j <= cities_num; j++)
{
cout << path[min_path][j] << " -> ";
outfile << path[min_path][j] << " -> ";
sum += topography.edges[ path[min_path][j] ][ path[min_path][j + 1] ];
}
cout << path[min_path][cities_num + 1] << endl;
cout << "最短路徑長度為:" << min_sum << endl;
cout << "DFS 次數為:" << count;
return 0;
}
獲取實驗數據
使用上述不同規模的城市拓撲分析TSP問題,得出的實驗數據如下。
城市規模(個) | 路線數(條) | DFS次數(次) | 數據文件大小(KB) |
---|---|---|---|
4 | 6 | 16 | 1 |
5 | 24 | 65 | 2 |
6 | 120 | 326 | 7 |
7 | 720 | 1957 | 45 |
8 | 5070 | 13700 | 348 |
9 | 40320 | 109601 | 3050 |
10 | 362880 | 986410 | 30260 |
11 | 3628800 | 9864101 | 330943 |
實驗數據分析
當使用蠻力法解決TSP問題時,需要考慮從某個城市出發的所有路線。由於城市之間彼此互通,城市拓撲是個完全圖,因此所有路線的數量規模是 n-1 個城市的全排列。
當輸入城市數量n時,會產生n!條路線,從而計算路徑長度的操作就需要執行n!次。也就是說蠻力法解決TSP 問題的 T(n) = n!,從而得出 O(n) = n!。無論是路線數、DFS 次數還是數據文件大小,都能明顯地體現這個趨勢。
路線數
DFS 次數
數據文件大小
總結
我一開始使用的是 C++ 的 new 運算符動態內存分配二維數組。但是除了城市規模 4 的數據下,其他的規模均無法正常運行,並且主函數 “return value 3221225477”。經過查閱資料得知這可能和未初始化的變量或指針引發的,但是我並不知道問題代碼及其原因,無奈之下只好直接定義了一個較大的二維數組進行存儲。
在測試城市規模 12 的數據時,由於棧區空間已經用盡,我打算使用動態內存分配使用堆區內存。結果需要分配的內存過多,導致所有內存空間全部被 C++ 占用,而 C++ 並沒有智能保護內存的機制,導致我的電腦直接宕機。在強行斷電並修復電腦之后覺定放棄 12 個城市的 TSP 問題求解。
蠻力法是解決問題明確而直接的手法,程序編寫較為簡單,思路是模擬情景下的所有可能性進行分析,在時間允許下是極佳的算法。但是在不同的情景下會存在效率低下的情況,我們會需要更加巧妙的算法提高解決問題的效率,期待接下來對算法的進一步學習,使用其他的算法對這些問題進行求解。
參考資料
《數據結構(C語言版|第二版)》—— 嚴蔚敏 李冬梅 吳偉民 編著,人民郵電出版社
《算法設計與分析(第二版)》——王紅梅,胡明 編著,清華大學出版社
Graphviz 安裝並使用 (Python)
C++文件和流
百度百科:TSP問題