1.问题
给定n个圆的半径序列,将它们放到矩形框中,各圆与矩形底边相切,求具有最小排列长度的圆排列。
2.解析
- 圆的摆放位置不同,得到的结果可能不同,所以这是一个排列问题,在回溯里面需要添加全排列的代码。
- 要得到圆排列的长度,我们需要知道最右边圆心的横坐标,+它的半径+第一个圆的半径。通过下图可知,第n个圆的圆心横坐标=与他相切的圆的横坐标+2*sqrt(r[n]*r[n-1])。因为当前圆可能和之前某一个圆相切,不一定就和前一个相切,所以需要使用for循环。
- 通过compute函数找到排列的最左端和最右端计算得到圆排列的长度,用变量lenmin记录当前最小圆排列长度。
- 通过递归算法dfs,算法搜索至叶节点时,得到新的圆排列方案。通过Compute计算该圆排列的长度,通过比较,对最优解进行优化更新。当节点并未处于树的叶节点时,需要继续dfs选择下一个要排列的圆。
3.设计
- center函数可以得到每个圆的圆心坐标。因为目标圆有可能与排在它之前的任一圆相切,故需用for循环一一判断。构造center函数来计算圆在当前圆排列中的横坐标。
- compute函数计算当前圆排列的长度。变量Min记录当前最小圆排列长度。用double r[N];记录圆的半径。double x[N];记录排列圆的圆心横坐标。找到排列的最左端 if(x[i]-r[i]<left) left=x[i]-r[i]; 找到排列的最右端if(x[i]+r[i]>right)计算出最右端 right=x[i]+r[i]; right-left就得到了圆排列的长度。之所以要判断,是因为会产生如下情况。
- 在dfs函数中,运用了回溯法。
1)首先计算排列长度,如果大于圆的个数,调用compute函数,否则,继续找圆。
2)先用swap(r[t],r[i])确定当前第t个圆的半径。
3)第t个圆选入后,用center(t);确定当前的圆心横坐标,即为当前排列长度。
4)计算是否是最小排列长度(r[1]+cen+r[t]<Min),如果是:入选所选的第t个圆,并调用dfs函数继续深入第t+1个圆。【剪枝】
5)用swap(r[t],r[j])恢复现场。
4.分析
- 时间复杂度:
1)排列树共有n!次计算;
2)每次计算圆心的过程中有O(n)次计算时间;
3)整体算法时间复杂度为T(n)=O((n+1)!) - 空间复杂度:S(n)=O(n)
- 改进:
1)1,2,…,n-1,n和n,n-1, …,2,1这种互为镜像的排列具有相同的圆排列长度,可以计算一半,减少一半的计算量;
2)所给的n个圆中有k个圆有相同的半径,则这k个圆产生的k!个完全相同的圆排列,只需要计算一个。