算法分析与实践 大作业

1、问题

给定n个圆的半径序列,将它们放到矩形框中,各圆与矩形底边相切,求具有最小排列长度的原序列。

2、解析

一般而言我们会想到这种做法:
算法分析与实践 大作业_第1张图片
但是这种只是其中一种,我们并没有考虑完全,还有一些如:
算法分析与实践 大作业_第2张图片
小球本来可以放到两个大球的中间,不增加长度的
这个就类似于最短路的问题,由此我们想到了回溯算法。
开始时设a=[r1,r2,……rn]是所给的n个元的半径,则相应的排列树由a[1:n]的所有排列构成。
cter(x)用来计算第n个圆的横坐标
设:圆1的横坐标是x1,半径是r1,圆2的横坐标是x2,半径是r2
**(r1+r2)2-(r1-r2)2=(x1-x2)^2
=>4r1r2=(x1-x2)^2
=>2√(r1r1)=x1-x2
**
compute函数计算当前排列长度
dfs为核心函数,便利每一种可能

3、设计

#include
#include
#include
using namespace std;
const int N = 100;
const int inf = 0x3f3f3f3f;
double minn = inf,x[N], r[N];              //分别为最小圆排列长度,每个圆心横坐标,每个圆半径
int n;
double bestr[N];                //最小圆排列的半径顺序

double cter(int n) {         
    double temp = 0;
    for (int j = 1; j < n; ++j) {
        double now = x[j] + 2.0 * sqrt(r[n] * r[j]);  //判断与之前的圆是否相切 
        if (now > temp)        //取所求圆心的最小值
            temp = now;
    }
    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<minn)
	{
		minn=high-low;
		for(int i=1;i<N;++i)
			bestr[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 now = cter(t);
            if (now + r[t] + r[1] < minn) {
                x[t] = now;
                dfs(t + 1);
            }
            swap(r[t], r[j]);     //还原
        }
    }
}
int main() {
    scanf("%d",&n);
    for (int i = 1; i <= n; ++i){
    	scanf("%lf", &r[i]);
	}
    dfs(1);
    printf("最小圆排列长度为:%.2f\n", minn);
    printf("最小圆排列的顺序对应的半径分别为:\n");
    for (int i = 1; i <= n; ++i) {
        if (i != 1)printf(" ");
        printf("%.2f", bestr[i]);
    }
    return 0;
}

结果算法分析与实践 大作业_第3张图片

4、分析

最坏情况下会遍历所有的解空间序列,此时的时间复杂度为O(n!)
每次排列都需要调用一次compute函数,时间复杂度为O(n)
综上,该算法的时间复杂度为O((n+1)!)

5、源码

https://github.com/zhaozhiliangzzl/-/blob/master/final.cpp

你可能感兴趣的:(算法分析与实践 大作业)