尋找最近點對(POJ 3714)

網上搜了一些講解,下邊這個感覺比較清晰:

講解:http://www.cnblogs.com/king1302217/archive/2010/07/08/1773413.html

  在二維平面上的n個點中,如何快速的找出最近的一對點,就是最近點對問題。

    一種簡單的想法是暴力枚舉每兩個點,記錄最小距離,顯然,時間複雜度爲O(n^2)。

    在這裏介紹一種時間複雜度爲O(nlognlogn)的算法。其實,這裏用到了分治的 思想。將所給平面上n個點的集合S分成兩個子集S1和S2,每個子集中約有n/2個點。然後在每個子集中遞歸地求最接近的點對。在這裏,一個關鍵的問題是 如何實現分治法中的合併步驟,即由S1和S2的最接近點對,如何求得原集合S中的最接近點對。如果這兩個點分別在S1和S2中,問題就變得複雜了。

    爲了使問題變得簡單,首先考慮一維的情形。此時,S中的n個點退化爲x軸上的n個實數x1,x2,...,xn。最接近點對即爲這n個實數中相差最小的兩個實數。顯然可以先將點排好序,然後線性掃描就可以了。但我們爲了便於推廣到二維的情形,嘗試用分治法解決這個問題。

    假設我們用m點將S分爲S1和S2兩個集合,這樣一來,對於所有的p(S1中的點)和q(S2中的點),有p<q。

    遞歸地在S1和S2上找出其最接近點對{p1,p2}和{q1,q2},並設

d = min{ |p1-p2| , |q1-q2| }

    由此易知,S中最接近點對或者是{p1,p2},或者是{q1,q2},或者是某個{q3,p3},如下圖所示。



 

    如果最接近點對是{q3,p3},即|p3-q3|<d,則p3和q3兩者與m的距離都不超過d,且在區間(m-d,d]和(d,m+d]各有且僅有一個點。這樣,就可以在線性時間內實現合併。

    此時,一維情形下的最近點對時間複雜度爲O(nlogn)。

    在二維情形下,類似的,利用分治法,但是難點在於如何實現線性的合併?



 

    由上圖可見,形成的寬爲2d的帶狀區間,最多可能有n個點,合併時間最壞情況下爲n^2,。但是,P1和P2中的點具有以下稀疏的性質,對於P1中的任意一點,P2中的點必定落在一個d X 2d的矩形中,且最多隻需檢查六個點(鴿巢原理)。

    這樣,先將帶狀區間的點按y座標排序,然後線性掃描,這樣合併的時間複雜度爲O(nlogn),幾乎爲線性了。

算法描述:已知集合S中有n個點,分治法的思想就是將S進行拆分,分爲2部分求最近點對。算法每次選擇一條垂線L,將S拆分左右兩部分爲SL和SR,L一般取點集S中所有點的中間點的x座標來劃分,這樣可以保證SL和SR中的點數目各爲n/2,

(否則以其他方式劃分S,有可能導致SL和SR中點數目一個爲1,一個爲n-1,不利於算法效率,要儘量保持樹的平衡性)

依次找出這兩部分中的最小點對距離:δL和δR,記SL和SR中最小點對距離δ = min(δL,δR),如圖1:

 

   

     以L爲中心,δ爲半徑劃分一個長帶,最小點對還有可能存在於SL和SR的交界處,如下圖2左圖中的虛線帶,p點和q點分別位於SL和SR的虛線範圍內,在這個範圍內,p點和q點之間的距離纔會小於δ,最小點對計算纔有意義。

    

 

Figure 2

 

 

      對於SL虛框範圍內的p點,在SR虛框中與p點距離小於δ的頂多只有六個點,就是圖二右圖中的2個正方形的6的頂點。這個可以反推證明,如果右邊這2個正方形內有7個點與p點距離小於δ,例如q點,則q點與下面正方形的四個頂點距離小於δ,則和δSLSR的最小點對距離相矛盾。因此對於SL虛框中的p點,不需求出p點和右邊虛線框內所有點距離,只需計算SR與p點y座標距離最近的6個點,就可以求出最近點對,節省了比較次數。

(否則的話,最壞情形下,SR虛框中有可能會有n/2個點,對於SL虛框中的p點,每次要比較n/2次,浪費了算法的效率

HDOJ1007:http://acm.hdu.edu.cn/showproblem.php?pid=1007

代碼如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const double INF = 1e20;
const int N = 100005;

struct Point
{
    double x;
    double y;
}point[N];
int n;
int tmpt[N];

bool cmpxy(const Point& a, const Point& b)
{
    if(a.x != b.x)
        return a.x < b.x;
    return a.y < b.y;
}

bool cmpy(const int& a, const int& b)
{
    return point[a].y < point[b].y;
}

double min(double a, double b)
{
    return a < b ? a : b;
}

double dis(int i, int j)
{
    return sqrt((point[i].x-point[j].x)*(point[i].x-point[j].x)
                + (point[i].y-point[j].y)*(point[i].y-point[j].y));
}

double Closest_Pair(int left, int right)
{
    double d = INF;
    if(left==right)
        return d;
    if(left + 1 == right)
        return dis(left, right);
    int mid = (left+right)>>1;
    double d1 = Closest_Pair(left,mid);
    double d2 = Closest_Pair(mid+1,right);
    d = min(d1,d2);
    int i,j,k=0;
    //分離出寬度爲d的區間
    for(i = left; i <= right; i++)
    {
        if(fabs(point[mid].x-point[i].x) <= d)
            tmpt[k++] = i;
    }
    sort(tmpt,tmpt+k,cmpy);
    //線性掃描
    for(i = 0; i < k; i++)
    {
        for(j = i+1; j < k && point[tmpt[j]].y-point[tmpt[i]].y<d; j++)//理解!!
        {
            double d3 = dis(tmpt[i],tmpt[j]);
            if(d > d3)
                d = d3;
        }
    }
    return d;
}


int main()
{
    while(true)
    {
        scanf("%d",&n);
        if(n==0)
            break;
        for(int i = 0; i < n; i++)
            scanf("%lf %lf",&point[i].x,&point[i].y);
        sort(point,point+n,cmpxy);
        printf("%.2lf\n",Closest_Pair(0,n-1)/2);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章