最小覆盖圆

本文详细介绍了如何在平面上寻找能够覆盖所有点的最小圆,包括对于不同点数情况下的处理方式,通过随机化和逐步扩展的方法,最终找到最优解。文章提供了完整的实现模板,并解释了算法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最小覆盖圆解决的问题模板大概是:
在一个平面内的很多点中,找出一个最小的圆,使之覆盖所有的点。

思路

假设点的个数为 nnn, 最小覆盖圆面积为 RRR
n=1n = 1n=1 时,毋庸置疑,此时 R=0R = 0R=0
n=2n = 2n=2 时,此时R应该为 R=dis(point1,point2)/2R = dis(point1, point2) / 2R=dis(point1,point2)/2
n=3n = 3n=3 时,那么 RRR 就是三个点形成的最小圆,也就是三个点形成的三角形的外接圆。

那么 n>3n > 3n>3 时呢?我们可以延续刚刚的思路

因为在取点的时候,可能第一次或者之后取的两个点三个点做出的圆就包含了所有点, 导致所求圆面积不是最小的,所以在计算之前需要把取点的顺序打乱,也就是random一下。 在这里插入图片描述

由于只需要三个点就可以确定一个圆了,其圆心就是三角形的外接圆圆心(两个点的话那就两点中点。。),那么我们来模拟一下过程:

① 首先,将所有点随机排列,使用random_shuffle() 函数。

② 设此时的最小圆圆心为O0,半径为R0。在点中取出一点 iii,若i在圆O0内,则遍历下一个点,否则就将其作为当前圆的圆心,R=0R = 0R=0,就是 n=1n = 1n=1的情况,因为我们需要构造新的圆,使之完全包括前i个点,有一个点i了,那就还需要再找两个。

③ 再依次取出i之前的点,作为第二个点j,如果没有一个是在R的外面的话就表示当前点i之前的所有的点都在我们之前计算的最小圆中,再进行下去就没有意义,所以我们需要遍历下一个i点,继续重复我们的计算,也就是i++,然后回到②。每当遇到一个不在R中的点,这时的 R=dis(i,j)/2R = dis(i, j) / 2R=dis(i,j)/2 ,就是 n=2n = 2n=2 的情况,然后就进行下一步。

④ 依次取出j之前的所有点, 作为第三个点k,同样地,如果没有一个点在当前我们计算的最小圆的外面,那么再进行下去也没有意义,就需要遍历下一个点j,也就是 j++j ++j++ ,然后回到③。每当遇到一个不在R中的点,就相当于找到了第三个点,这三个点都刚好不在我们最初计算的圆O0中,而且刚好能做出一个圆刚刚好包含点1~k的圆且,点i、j也都在这个圆的边界上。

那么 k+1 j−1k + 1 ~ j - 1k+1 j1 之间的点呢?所以我们需要继续重复次步骤,也就是把j之前的所有点都当作一次第三个点,从而计算出能刚刚好包含前 j−1j - 1j1 个点的圆同理,②、③的回溯也是这个原因,这样一直计算到i == n的话,求出的圆就是能包含n个点的最小圆了。

所以此处只需要三重循环,再加三个判断就能计算了,额这个算法的时间复杂度。其实只有 O(n)O(n)O(n) 啦,算一下。

tips:
求三角形的外接圆,可以用直线方程、也可以运用参数方程,从而求得两条直线的中垂线,其焦点即为圆心。也可以运用向量的思想求得。

实现模板:
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
const double eds = 1e-12;

struct Node{
    double x, y;
}node[maxn];// 点
double R = 0; //半径

double sq(double x) {return x * x;}

double dis(double x1, double y1, double x2, double y2) {return sqrt(sq(x1 - x2) + sq(y1 - y2));}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(); cout.tie(0);

    int n; cin >> n;
    for(int i = 0; i < n; i ++) {
        cin >> node[i].x >> node[i].y;
    }
    
    random_shuffle(node, node + n);//将点随机排列,防止好心人教你wa题
    double r = node[0].x, l = node[0].y; //圆心的横、纵坐标
    double x1, x2, x3, y1, y2;
    for(int i = 1; i < n; i ++){ 
        if(dis(node[i].x, node[i].y, r, l) - R > eds){ //不在半径为R的圆内
            x1 = node[i].x, y1 = node[i].y;
            r = x1; l = y1; R = 0;
            for(int j = 0; j < i; j ++){
                if(dis(node[j].x, node[j].y, r, l) - R > eds){  //不在半径为R的圆内
                    x2 = node[j].x, y2 = node[j].y;
                    r = (x1 + x2) / 2.0; l = (y1 + y2) / 2.0;
                    R = dis(r, l, x2, y2);
                    for(int k = 0; k < j; k ++){
                        if(dis(node[k].x, node[k].y, r, l) - R > eds){  //不在半径为R的圆内
                            x3 = node[k].x, y3 = node[k].y;
                            double A = sq(x1) + sq(y1), B = sq(x2) + sq(y2), C = sq(x3)+ sq(y3);
                            double u1 = x1 - x2, u2 = x1 - x3, u3 = x2 - x3;
                            double v1 = y1 - y2, v2 = y1 - y3, v3 = y2 - y3;
                            l = ((C - A) * u1 - (B - A) * u2) / (2 * v1 * u2 - 2 * v2 * u1);
                            r = ((C - A) * v1 - (B - A) * v2) / (2 * u1 * v2 - 2 * u2 * v1);
                            R = dis(x1, y1, r, l);
                        }
                    }
                }
            }
        }
    }
    cout << R * 2.0 << "\n";
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值