O(n)最小圆覆盖算法 (随机增量法)

为什么叫随机增量我也不知道
首先考虑一个点集A,设他的最小覆盖圆为g(A)。 g(A)是存在且唯一的。 (假设存在两个,则必定存在更小覆盖圆)
而且g(A)必定满足以下两者之一:
1. 圆上有三个(或以上)A中的点
2. 圆上有两个A中的点并且其连线为直径。
若上述条件不成立则显然有更小覆盖圆。(感性调整)

就像这样:
这里写图片描述

记K(A)为A中在g(A)上的点集合,这样的点下文叫做关键点。

下面给出一个关键的定理

若点b不在g(A)内,则b属于K(A+b)。

反证:
假设b不属于K(A+b),
则b在g(A+b)内(不是关键点),那么g(A)与g(A+b)是同圆(有没有他都一样)。又因为b不在g(A)内,矛盾。

设g(A,x)为,点集A的最小覆盖圆,并且满足x在圆上。 (x,y不是A中的点)
设g(A,x,y)为,点集A的最小覆盖圆,并且满足x,y在圆上
类似的,我们可以证明:
若点b不在g(A,x)内,则b是g(A,x)的关键点
若点b不在g(A,x,y)内,则b是g(A,x,y)的关键点

先对点随机排序。 这对我们的复杂度分析至关重要。
已知1..i-1的最小覆盖圆C的圆心与半径。
g(1)=圆心为点1,半径为0.

从2开始,我们顺序求g(1..i)。

FirstStep: 对于一个新的i

若i在C中则不需要额外操作。
否则,由上面证明的定理可得i在g(1..i) (点1..i的最小覆盖圆,下面都这么简写了)上。
只要求得g(1..i-1,i)即可得到g(1..i)。,相当于此时进入一个子过程变相求当前g(i)

转化1

初始圆=圆心为点i,半径为0圆。
顺序求g(1..i-1,i)

依次令j=从1到i-1,若点j不在g(1..j-1,i)中(若在则不需要额外操作),则同理得j在g(1..j,i)上。
只要求得g(1..j-1,i,j)即可得到g(1..j,i),相当于此时进入一个子过程变相求当前g(j,i)
(点集为1..j并且i在圆上的最小覆盖圆,表示有点不严谨理解一下)

转化2

初始圆=点i,j为直径所成的圆。
顺序求g(1..j-1,i,j)

依次令k=从1到j-1,若k不在g(1..k-1,i,j)中,则同理得k在g(1..k,i,j)上。

这下我们就知道g(1..k,i,j)上有三个点i,j,k了,不需要继续找在圆上的点了。 因此将圆设为找到的定圆,继续直到求出g(1..j-1,i,j)为止。

时间复杂度的证明

前提是随机序列!
关键分析,第一层来说: 对于一个新的点i,i在不当前圆中的概率为3/i.
证明: 因为g(1..i)只由至多三个关键点决定。这三个关键点是从1..i中选三个。选中i时才会进入下一层 (蜜汁证明….怪怪的但随机情况好像正确)

第二三层类似,然后分析一下复杂度就可以得到期望O(n)的结论。

实现

g是当前圆心,r是当前半径。

    for (int i=1; i<=10*n; i++) { //随机打乱。
        int x=rand()%n+1,y=rand()%n+1;
        swap(p[x],p[y]);
    }
    g=(P) {p[1].x,p[1].y}; r=0;
    for (int i=2; i<=n; i++) if (!zai(p[i])) {
        g=(P){p[i].x,p[i].y}; r=0;
        for (int j=1; j<i; j++) if (!zai(p[j])) {
            g=mid(p[i],p[j]);
            r=sqrt(dis2(p[i],p[j]))*0.5;
            for (int k=1; k<j; k++) if (!zai(p[k])) {
                make(p[i],p[j],p[k]);
            }
        }
    }

make是求出三点所得的圆。 长这样

struct P{db x,y;};
struct L{db k,b;};
L zx(P g,db k,db kx) {
    if (kx==0) return (L){G,g.x}; return (L){k/kx,g.y-k/kx*g.x};
}
P jiaodian(L u,L v) {
    if (v.k==G) swap(u,v);
    db x=0;
    if (u.k==G) x=u.b; else x=(v.b-u.b)/(u.k-v.k);
    return (P) {x,v.b+v.k*x};
}
void make(P a,P b,P c) {
    P yx=jiaodian(zx(mid(a,b),-(b.x-a.x),(b.y-a.y)),zx(mid(a,c),-(c.x-a.x),(c.y-a.y)));
    g=yx; r=sqrt(dis2(yx,a));
}

就是求中垂线交点。 注意特判与y轴平行的情况。 (本来想打向量的但是就这么一丢丢特判一下算了。。。)

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
最小外包算法(Minimum Enclosing Circle)是一种寻找平面点集最小外接算法。以下是一份C++实现的源码: ```c++ #include <bits/stdc++.h> using namespace std; const int N = 100005; const double eps = 1e-8; const double PI = acos(-1.0); struct Point { double x, y; }p[N], q[N], O; int n; double dis(Point a, Point b) { return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); } double cross(Point a, Point b, Point c) { return (b.x-a.x)*(c.y-a.y) - (c.x-a.x)*(b.y-a.y); } Point getO(Point A, Point B, Point C) { double a1 = B.x-A.x, b1 = B.y-A.y, c1 = (a1*a1+b1*b1)/2; double a2 = C.x-A.x, b2 = C.y-A.y, c2 = (a2*a2+b2*b2)/2; double d = a1*b2-a2*b1; O.x = A.x+(c1*b2-c2*b1)/d; O.y = A.y+(a1*c2-a2*c1)/d; return O; } void MEC() { random_shuffle(p+1, p+1+n); O = p[1]; double R = 0; for(int i = 2; i <= n; i++) { if(dis(O, p[i]) < R+eps) continue; O = p[i]; R = 0; for(int j = 1; j < i; j++) { if(dis(O, p[j]) < R+eps) continue; O.x = (p[i].x + p[j].x) / 2.0; O.y = (p[i].y + p[j].y) / 2.0; R = dis(O, p[i]); for(int k = 1; k < j; k++) { if(dis(O, p[k]) < R+eps) continue; O = getO(p[i], p[j], p[k]); R = dis(O, p[i]); } } } } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%lf%lf", &p[i].x, &p[i].y); } MEC(); printf("%.2f %.2f %.2f\n", O.x, O.y, dis(O, p[1])); return 0; } ``` 其中,dis函数计算两点之间的距离,cross函数计算向量OA和向量OB的叉积,getO函数计算以A、B、C三点为直径的心坐标。最小外包算法的核心部分在MEC函数中,利用随机增量和三点定来找到最小
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值