问题
圆排列问题:给定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!