1.问题描述
给定n个大小不等的圆c1,c2,…,cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。例如,当n=3,且所给的3个圆的半径分别为1、1、2时,这3个圆的最小长度的圆排列如图,其最小长度为2+4√2。
2.算法分析
该问题是一个排列问题,问题的解存储在序列r={r1,r2,r3…}中,问题的解空间树是一颗排列树。用x={x1,x2,x3…}表示当前排列的各个圆的圆心。
- 下界函数:用Center(t)计算新增圆的圆心,每次新增一个圆则排列的序列会增长,比较center(t)+r[t]+r[0]这个值(注意不是序列中总长,只是t号圆的最右端的长度)和已知序列长度min比较,如果大于min则剪枝。
- 实现:遍历r[0:t-1]序列,假设新增圆与其相切,
(rt+rj)^2-(rt-rj)^2=4*rj*rt=>x[t]=x[i]+2.0*sqrt(r[t]*r[i])
float center(int t)//计算t圆的圆心x坐标
{
float res=0;
for(int i=0;i<t;i++)
{//(rt+rj)^2-(rt-rj)^2=4*rj*rt
float temp=x[i]+2.0*sqrt(r[t]*r[i]);
if(temp>res) res=temp;
}
return res;
}
- 用compute计算整个序列的长度,遍历每一个圆,记录左端low和右端high,迭代得到low的最小值和hight的最大值,最后做差即可得到序列长度。
void compute()
{
float low=0,high=0;
for(int i=0;i<n;i++)
{
float tempLow=x[i]-r[i];
float rempHigh=x[i]+r[i];
if(tempLow<low) low=tempLow;
if(rempHigh>high) high=rempHigh;
}
if(high-low<minx) minx=high-low;
}
3.算法实现
#include <iostream>
#include <math.h>
using namespace std;
const int maxn=100005;
int n;
float minx=1e7;
float x[maxn]={0},r[maxn]={0};
float center(int t)//计算t圆的圆心x坐标
{
float res=0;
for(int i=0;i<t;i++)
{//(rt+rj)^2-(rt-rj)^2=4*rj*rt
float temp=x[i]+2.0*sqrt(r[t]*r[i]);
if(temp>res) res=temp;
}
return res;
}
void compute()
{
float low=0,high=0;
for(int i=0;i<n;i++)
{
float tempLow=x[i]-r[i];
float rempHigh=x[i]+r[i];
if(tempLow<low) low=tempLow;
if(rempHigh>high) high=rempHigh;
}
if(high-low<minx) minx=high-low;
}
void circleBacktrace(int t)
{
if(t>=n) compute();//更新minx
else{
for(int i=t;i<n;i++)
{
swap(r[t],r[i]);
float c=center(t);//圆心
if(c+r[t]+r[0]<minx)
{
x[t]=c;
circleBacktrace(t+1);
}
swap(r[t],r[i]);
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>r[i];
circleBacktrace(0);
printf("The min arrengment is %f\n",minx);
system("pause");
return 0;
}
/*
3
1 1 2
*/
4.算法复杂度
- 时间复杂度:对于排列树,共有n!次计算;另外在每次计算圆心的过程中有O(n)次计算时间,因此整体算法时间复杂度为T(n)=O((n+1)!)
- 空间复杂度:S(n)=O(n)