算法分析与设计
圆排列问题
问题描述:
给定n个圆的半径序列,将它们放到矩形框中,各圆与矩形底边相切,求具有最小排列长度的圆排列。
算法
假设三个圆的半径分别为1,1,2。那么这种情况的圆排列长度为2+4sqrt(2)。计算r1=1,r2=2情况下的两圆心的距离为len2=(r1+r2)2-(r2-r1)^2。推广到所有情况就可以得到len=2sqrt(r1*r2)。
这是n=3时,圆A,圆B,圆C的排列数的情况。ABC,ACB,BCA,BAC,CAB,CBA。圆排列问题的解空间是一颗排列树。按照回溯法搜索排列树的算法框架,设开始时r=[r1,r2,…,rn]是所给的n个圆的半径,则相应的排列树由r[1:n]的所有排列构成。
compute函数:可以想象其中任意的一个圆无限大或无限小,无限大的话那其余的圆就可以统统忽略了。已知所有圆的x[]和r[],求出每个圆的左右坐标,通过比较找出最小的左部坐标和最大的右部坐标,想减就是该圆排列的长度,每次更新出最小的minn。
核心代码:
double Getc(int t) { //计算第t个圆的圆心坐标
double temp = 0;
for (int j = 1; j < t; ++j) {
double val = x[j] + 2.0 * sqrt(r[t] * r[j]); //判断与他之前的所有圆相切的情况
temp=max(temp,val);
}
return temp;
}
void compute() {
double L = inf, R = 0; //L是左边界,R是有边界
for (int i = 1; i <= n; ++i) {
if (x[i] - r[i] < L)
L = x[i] - r[i];
if (x[i] + r[i] > R)
R = x[i] + r[i];
}
if (R - L < minn) {
minn = R - L;
for (int i = 1; i <= n; ++i)
arr[i] = r[i];
}
}
void dfs(int t) {
if (t == n + 1) {
compute();
}
else {
for (int j = t; j <= n; ++j) {
swap(r[t], r[j]);
double val = Getc(t);
if (val + r[t] + r[1] < minn) {
x[t] = val;
dfs(t + 1);
}
swap(r[t], r[j]); //还原
}
}
}
时间复杂度:
算法遍历解空间,时间复杂度为O(n!)。
每次计算圆排列长度,此时的时间复杂度为O(n)。
综上,该算法的时间复杂度为O(n!*n)。
源码:
https://github.com/SpiritDemon-max/myText/blob/master/circle.cpp