1.問題
給定n個大小不等的圓c1,c2,…,cn,現要將這n個圓排進一個矩形框中,且要求各圓與矩形框的底邊相切。圓排列問題要求從n個圓的所有排列中找出有最小長度的圓排列。例如,當n=3,且所給的3個圓的半徑分別為1,1,2時,這3個圓的最小長度的圓排列如圖所示。其最小長度為。
2. 解析
圓排列問題的解空間是一棵排列樹,按照回溯法搜索排列數的算法框架
設所有圓的半徑為 A = [r1 , r2 , r3 … , rn];
那么對應的排列數有 A[1 : n ]的全排列構成
那么我們通過回溯法計算最優圓排列的時候需要注意幾個問題
(1)首先是相切問題:
如圖:
在思考本題是很容易先入為主的認為,相鄰的圓是相切的,但是實際上通過這個圖我們可以知道,最后一個圓是有可能和它之前的任意一個圓相切的,如果以相鄰圓相切的思路做題,得出的結果有可能會偏小
(2)其次是剪枝問題:
在圓排列問題中我們需要構造全排列,通過排列組合可以知道構造全排列的時間復雜度為O(n!),但是實際上有一些圓排列在僅有一部份圓的時候其長度就已經超過了最小長度,因此有這些部分圓排列所演變出的圓排列明顯是不符合的。
這可以在某些情況下減少算法的時間復雜度。
3.設計
計算當前圓的橫坐標
1 double get_center(int t) 2 { 3 double tmp = 0; 4 for (int i = 1; i < t; ++i) 5 { 6 double val = x[i] + 2.0 * sqrt(r[t] * r[i]); //. 目標圓T有可能能夠和排在它之前的任意一個圓相切,因此需要以一判斷取最大值 7 tmp = max(tmp, val); 8 } 9 return tmp; 10 }
通過回溯法夠成圓排列的全排列,因為用於記錄圓橫坐標的x數組,定義為了全局變量,因此第一個圓的橫坐標被默認設置為了0,在計算圓排列長度進行剪枝的時候需要加上第一個圓的半徑。
1 void dfs(int pos) 2 { 3 if (pos == n + 1) 4 { 5 get_ans(); 6 } 7 else 8 { 9 for (int i = pos; i <= n; ++i) 10 { 11 swap(r[pos], r[i]);//構造全排列 12 double X_pos = get_center(pos);//獲取當前 13 if (X_pos + r[pos] + r[1] < minlen) //進行一定程度上的減枝 14 { 15 x[pos] = X_pos; 16 dfs(pos + 1); 17 } 18 swap(r[pos], r[i]); //構造全排列 19 } 20 } 21 }
4.分析
由排列組合可知,生成一個長度為n的序列的全排列的時間復雜度為O(n!)
同時在這個算法中,對於每一個排列中的每一個圓,它有可能和在它之前的任意一個圓相切,為了正確的確定是與那個圓相切需要使用for循環進行遍歷,通過遍歷找到與之相切的圓,其每次的時間復雜度為O(n)
綜上所述,計算最小圓排列的復雜度為O(n * n!)
5.源碼

1 #include<iostream> 2 #include<cmath> 3 #include<algorithm> 4 using namespace std; 5 6 7 const int maxn = 1e5 + 10; 8 double minlen = 1e5; 9 double x[maxn], r[maxn]; 10 // x存儲每個圓心的橫坐標 , r存儲每個圓的半徑 , 11 // 由於x數組為全局變量,因此初始化為0,因此第一個圓的橫坐標默認為0,因此在計算最小圓排列長度的時候還需要注意加上第一個圓的半徑 12 double bestR[maxn]; // 存儲最優的圓排列的半徑順序 13 int n; 14 15 double get_center(int t) 16 { 17 double tmp = 0; 18 for (int i = 1; i < t; ++i) 19 { 20 double val = x[i] + 2.0 * sqrt(r[t] * r[i]); //. 目標圓T有可能能夠和排在它之前的任意一個圓相切,因此需要以一判斷取最大值 21 tmp = max(tmp, val); 22 } 23 return tmp; 24 } 25 26 void get_ans() 27 { 28 double minn = 0, maxx = 0; // 計算最優圓排列的最左端 和 最右端 29 for (int i = 1; i <= n ; ++i) 30 { 31 if (x[i] - r[i] < minn) minn = x[i] - r[i]; 32 if (x[i] + r[i] > maxx) maxx = x[i] + r[i]; 33 } 34 if (maxx - minn < minlen) 35 { 36 minlen = maxx - minn; // 更新最小圓排列 37 for (int i = 1; i <= n ; ++i) 38 { 39 bestR[i] = r[i]; 40 } 41 } 42 } 43 44 45 void dfs(int pos) 46 { 47 if (pos == n + 1) 48 { 49 get_ans(); 50 } 51 else 52 { 53 for (int i = pos; i <= n; ++i) 54 { 55 swap(r[pos], r[i]);//構造全排列 56 double X_pos = get_center(pos);//獲取當前 57 if (X_pos + r[pos] + r[1] < minlen) //進行一定程度上的減枝 58 { 59 x[pos] = X_pos; 60 dfs(pos + 1); 61 } 62 swap(r[pos], r[i]); //構造全排列 63 } 64 } 65 } 66 67 68 int main() 69 { 70 cout << "輸入圓的個數:" << endl; 71 cin >> n; 72 cout << "依次輸入圓的半徑" << endl; 73 for (int i = 1; i <= n; ++i) 74 { 75 cin >> r[i]; 76 } 77 for (int i = 1; i <= n; ++i) 78 { 79 cout << "第" << i << "個圓的半徑為:" << r[i] << endl; 80 } 81 dfs(1); 82 cout << "最小圓排列的長度為:" << minlen << endl; 83 cout << "最優原排列的順序對應的半徑分別為:"; 84 for (int i = 1; i <= n; ++i) 85 { 86 cout << bestR[i] << " "; 87 } 88 cout << endl; 89 return 0; 90 }
https://github.com/BambooCertain/Algorithm.git