1. 程式人生 > >O(n)最小圓覆蓋演算法 (隨機增量法)

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軸平行的情況。 (本來想打向量的但是就這麼一丟丟特判一下算了。。。)