程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 編程之美2.11——尋找最近點對(POJ 3714)

編程之美2.11——尋找最近點對(POJ 3714)

編輯:C++入門知識

問題:
給定平面上N個點的坐標,找出距離最近的兩個點。

解法:
我們先對N個點的x坐標進行排序,排序我們使用最壞復雜度O(n*logn)的快速排序方法,在排序的過程中minDifferent會遞歸計算出左右兩邊的最小距離,再用其中的較小值minum得到以中位數點附近的帶狀區域[p[median+1].x-median, p[median].x+median],對帶狀區域的點按照y坐標排序,對帶狀區域的每個點只需計算最多7個點,就能得到所有可能小於minum的點對。
[cpp] 
#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <cmath> 
using namespace std; 
 
// 頂點信息 
struct Point 

    double m_x, m_y; 
    Point():m_x(0.0),m_y(0.0) {} 
    Point(double x, double y):m_x(x),m_y(y){} 
    bool operator==(const Point& p) const  
    {return m_x==p.m_x && m_y==p.m_y;} 
}; 
 
ostream& operator<<(ostream& os, const Point& p) 

    return os << "(" << p.m_x << "," << p.m_y << ")"; 

 
// 插入排序 
template<class T, class Pr> 
void insert_sort(vector<T> &vec, int l, int r, Pr pred) 

    int i, j; 
    for (i=l+1; i<=r; i++) 
    { 
        T tmp = vec[i]; 
        for (j=i-1; j>=l && pred(tmp,vec[j]); j--)  
            vec[j+1]=vec[j]; 
        vec[j+1] = tmp; 
    } 

 
// 找到key所在的位置 
template<class T> 
int get_position(vector<T> &vec, int l, int r, T key) 

    for (int i=l; i<=r; i++) 
        if (key == vec[i]) 
            return i; 
    return -1; 

 
// 按第一個元素對vec進行劃分 
template<class T, class Pr> 
int partition(vector<T> &vec, int l, int r, Pr pred) 

    int i, j; 
    for (i=l+1,j=l; i<=r; i++) 
    { 
        if (pred(vec[i],vec[l])) 
        { 
            ++j; 
            swap(vec[i],vec[j]); 
        } 
    } 
    swap(vec[j],vec[l]); 
    return j; 

 
// 順序統計得到第k個元素的值 
template<class T, class Pr> 
T select(vector<T> &vec, int l, int r, int k, Pr pred) 

    int n = r-l+1; 
    if (n==1) 
    { 
        if (k!=0) 
            printf("Out of Boundary!\n"); 
        return vec[l]; 
    } 
    // 找中位數的中位數作為分割點 
    int cnt = n/5; 
    int tcnt = (n+4)/5; 
    int rem = n%5; 
    vector<T> group(tcnt); 
    int i, j; 
    for (i=0,j=l; i<cnt; i++,j+=5) 
    { 
        insert_sort(vec, j, j+4, pred); 
        group[i] = vec[j+2]; 
    } 
    if (rem) 
    { 
        insert_sort(vec, j, j+rem-1, pred); 
        group[i] = vec[j+(rem-1)/2]; 
    } 
    T key = select(group, 0, tcnt-1, (tcnt-1)/2, pred); 
    // 找到分割點的位置 
    int key_pos = get_position(vec, l, r, key); 
    swap(vec[key_pos], vec[l]); 
    // 用分割點對數組進行劃分,小的在左邊,大的在右邊 
    int pos = partition(vec, l, r, pred); 
    int x = pos - l; 
    if (x == k) return key; 
    else if (x < k)  
        return select(vec, pos+1, r, k-x-1, pred); 
    else 
        return select(vec, l, pos-1, k, pred); 

 
// 計算點a和b的距離 
double dist(const Point& a, const Point& b) 

    double x = a.m_x-b.m_x; 
    double y = a.m_y-b.m_y; 
    return sqrt(x*x+y*y); 

 
bool cmpX(const Point& a, const Point& b) 

    return a.m_x < b.m_x; 

 
bool cmpY(const Point& a, const Point& b) 

    return a.m_y < b.m_y; 

 
double minDifferent(vector<Point> p, int l, int r, vector<Point> &result) 

    // 按中位數進行劃分後的子區域的元素個數都會減小到2或3,不會再到1 
    if ((r-l+1)==2) 
    { 
        result[0] = p[l]; 
        result[1] = p[r]; 
        if (cmpX(p[r],p[l])) swap(p[l], p[r]); 
        return dist(p[l], p[r]); 
    } 
    if ((r-l+1)==3) 
    { 
        insert_sort(p, l, r, cmpX); 
        double tmp1 = dist(p[l], p[l+1]); 
        double tmp2 = dist(p[l+1], p[l+2]); 
        double ret = min(tmp1, tmp2); 
        if (tmp1 == ret) 
        { 
            result[0] = p[l]; 
            result[1] = p[l+1]; 
        } 
        else 
        { 
            result[0] = p[l+1]; 
            result[1] = p[l+2]; 
        } 
        return ret; 
    } 
    // 大於3個點的情況 
    int mid = (r+l)>>1; 
    Point median = select(p, l, r, mid-l, cmpX); 
    vector<Point> res1(2), res2(2); 
    double min_l = minDifferent(p, l, mid, res1); 
    double min_r = minDifferent(p, mid+1, r, res2); 
    double minum = min(min_l, min_r); 
    if (minum == min_l) 
    { 
        result[0] = res1[0]; 
        result[1] = res1[1]; 
    } 
    else 
    { 
        result[0] = res2[0]; 
        result[1] = res2[1]; 
    } 
    // 對[p[mid+1]-minum, p[mid]+minum]的帶狀區域按y排序 
    vector<Point> yvec; 
    int i, j; 
    for (i=mid+1; i<=r; i++) 
        if (p[i].m_x - p[mid].m_x < minum) 
            yvec.push_back(Point(p[i])); 
    for (i=mid; i>=l; i--) 
        if (p[mid+1].m_x - p[i].m_x < minum) 
            yvec.push_back(Point(p[i])); 
    sort(yvec.begin(), yvec.end(), cmpY); 
    for (i=0; i<yvec.size(); i++) 
    { 
        // 至多只有與其後最多7個點的距離會小於minum 
        for (j=i+1; j<yvec.size() && yvec[j].m_y-yvec[i].m_y<minum && 
            j<=i+7; j++) 
        { 
            double delta = dist(yvec[i],yvec[j]); 
            if (delta < minum) 
            { 
                minum = delta; 
                result[0] = yvec[i]; 
                result[1] = yvec[j]; 
            } 
        } 
    } 
    return minum; 

 
int main() 

    int n, i, j, x, y; 
    vector<Point> result(2); 
    vector<Point> input; 
    cin >> n; 
    for (i=0; i<n; i++) 
    { 
        cin >> x; 
        cin >> y; 
        input.push_back(Point(x,y)); 
    } 
    double minum = minDifferent(input, 0, input.size()-1, result); 
    cout << "nearest point: " << result[0] << " and "  
         << result[1] << endl; 
    cout << "distance: " << minum << endl; 
    return 0; 

POJ 3714 問題:
平面上有兩類點,計算屬於不同類的頂點對的最小值。
解法:參考http://blog.csdn.net/smsmn/article/details/5963487
算法思想與上面基本相同,但編程方式上進行了改變,更好理解。下面的代碼寫法上完全按照參考代碼的思路,只是將數組操作改為vector,但提交到POJ 3714上卻會TLE,看來動態分配空間所占用的時間也不小。所以要想AC,請使用參考代碼。
[cpp] 
#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <cmath> 
using namespace std; 
 
const double INF = 1e100; 
 
struct Point 

    double x, y; 
    int flag;   // 頂點的類別 
    Point(){}  www.2cto.com
    Point(double xx, double yy):x(xx),y(yy){} 
}; 
 
vector<Point> p; 
 
bool cmp1(const Point& a, const Point& b) 

    return a.x < b.x; 

 
bool cmp2(int a, int b) 

    return p[a].y < p[b].y; 

 
 
double dist(const Point& a, const Point& b) 

    double xx = a.x - b.x; 
    double yy = a.y - b.y; 
    return sqrt(xx*xx+yy*yy); 

 
// 輸出屬於不同類的頂點對的最小值 
double min_dist(vector<Point> p, int left, int right) 

    int mid = (left+right)>>1, i,j; 
    if (left>=right) return INF; 
    for (i=mid; i>=left && p[mid].x<=p[i].x; i--); 
    double minum = min_dist(p, left, i); 
    for (i=mid; i<=right && p[i].x<=p[mid].x; i++); 
    minum = min(minum, min_dist(p, i, right)); 
    vector<int> yp; 
    for (i=mid; i>=left && p[mid].x-p[i].x<minum; i--) 
        yp.push_back(i); 
    for (i=mid+1; i<=right && p[i].x-p[mid].x<minum; i++) 
        yp.push_back(i); 
    // 這個方法非常巧妙,直接對頂點索引進行排序,減少了空間使用, 
    // 代碼上也更加簡潔 
    sort(yp.begin(), yp.end(), cmp2); 
    for (i=0; i<yp.size(); i++) 
        for (j=i+1; j<yp.size() && p[yp[j]].y-p[yp[i]].y<minum; j++) 
            // 主要的不同之處,產生最小距離的點對必須屬於不同類別 
            if (p[yp[j]].flag != p[yp[i]].flag) 
                minum = min(minum, dist(p[yp[j]], p[yp[i]])); 
    return minum; 

 
int main() 

    int i,j,test; 
    cin >> test; 
    while (test--) 
    { 
        int xx,yy,n; 
        cin >> n; 
        for (i=0; i<n; i++) 
        { 
            cin >> xx >> yy; 
            p.push_back(Point(xx,yy)); 
            p[i].flag = 1; 
        } 
        for (; i<2*n; i++) 
        { 
            cin >> xx >> yy; 
            p.push_back(Point(xx,yy)); 
            p[i].flag = 2; 
        } 
        // 按照x坐標對點集進行排序 
        sort(p.begin(), p.end(), cmp1); 
        printf("%.3lf\n", min_dist(p, 0, p.size()-1)); 
    } 

作者:linyunzju

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved