問題
圓排列問題:給定n個圓的半徑序列,將它們放到矩形框中,各圓與矩形底邊相切,
求具有最小排列長度的圓排列。
解析
圓排列問題的解空間是一棵排列樹。按照回溯法搜索排列樹的算法框架,設開始時a=[r1,r2,……rn]是所給的n個元的半徑,則相應的排列樹由a[1:n]的所有排列構成。
1. center計算圓在當前圓排列中的橫坐標,由x^2 = sqrt((r1+r2)^2-(r1-r2)^2)推導出x = 2*sqrt(r1*r2)。
2.Compute計算當前圓排列的長度。變量lenmin記錄當前最小圓排列長度。數組r存儲所有圓的半徑。數組x則記錄當前圓排列中各圓的圓心橫坐標。
3. 在遞歸算法Backtrack中,當i>n時,算法搜索至葉節點,得到新的圓排列方案。此時算法調用Compute計算當前圓排列的長度,適時更新當前最優值。當i<n時,當前擴展節點位於排列樹的i-1層。此時算法選擇下一個要排列的圓,並計算相應的下界函數。
代碼

#include<iostream> #include<cmath> #include<algorithm> using namespace std; const int N=4; double minlen=10000,x[N],r[N];//分別為最小圓排列長度,每個圓心橫坐標,每個圓半徑 double bestr[N];//最小圓排列的半徑順序 double center(int t)//得到每個圓的圓心坐標 { double temp=0; for(int j=1;j<t;++j)//因為目標圓有可能與排在它之前的任一圓相切,故需一一判斷 { double xvalue=x[j]+2.0*sqrt(r[t]*r[j]);// if(xvalue>temp) temp=xvalue; } return temp; } void compute() { double low=0,high=0; for(int i=1;i<N;++i) { if(x[i]-r[i]<low) low=x[i]-r[i]; if(x[i]+r[i]>high) high=x[i]+r[i]; } if(high-low<minlen) { minlen=high-low; for(int i=1;i<N;++i) bestr[i]=r[i]; } } void backtrack(int t) { if(t==N) { compute(); } else { for(int j=t;j<N;++j) { swap(r[t],r[j]); double centerx=center(t); if(centerx+r[t]+r[1]<minlen) { x[t]=centerx; backtrack(t+1); } swap(r[t],r[j]); } } } int main() { r[1]=1,r[2]=1,r[3]=2; cout<<"每個圓的半徑分別為:"; for(int i=1;i<N;++i) cout<<r[i]<<' '; cout<<endl; backtrack(1); cout<<"最小圓排列長度為:"<<minlen<<endl; cout<<"最優圓排列的順序對應的半徑分別為:"; for(int i=1;i<N;++i) cout<<bestr[i]<<' '; cout<<endl; return 0; }
時間復雜度
O(n) = n!