最近點對問題的分治演算法分析與實現

來源:互聯網
上載者:User

 為了方便起見,在這也先說明一下什麼是"最近點對問題".設有N個點,則易知
從這N個點中任意選取2個點作為一對一共有N(N-1)/2種組合, "最近點對問題"就是從這
N(N-1)/2個點對中求出相距最短的一對, 並求出它們之間的距離.

為了方便演算法的描述, 也把源碼貼一下:
typedef struct _Point {
    double x;
    double y;
} Point;

typedef struct _PointMap {
    Point point;
    int index; /* 這個成員的作用見文章後邊的解釋 */
#define poi_x   point.x
#define poi_y   point.y
} PointMap;
 
跟據問題描述, 我們很容易就會得到一種O(N^2)的解法, 即是: 窮舉所有的點對, 從中
出距離最短的. 該演算法用c語言描述如下:

 

const double EPS=0.00000000001;

const double MAXDIS=9e300;

MinDis( const PointMap M[], int n )
{
    assert( n >= 1 );
    if ( n == 1 )
        return 0.0;
    int i, j;
    double mindis = MAXDIS, tmp;
    for ( i = 0; i < n; i++ )
        for ( j = i + 1; j < n; j++ )
            if ( mindis > ( tmp = Dis( M[i], M[j] ) ) )
                mindis = tmp;
    return sqrt( mindis );
}
夠簡單吧? 呵呵.............

開始進入正題, 我們知道分治的思想就是把一個問題遞迴分解成較小的問題, 然後從子問
題的解中構建出原問題的解. 根據這個思想我們分析"最近點對問題"可知, 我們很容易就
可以對該問題進行分治. 我們可以先取某一個x值作為分界線把所有的點分為兩部分
                          |     
          ... .... .......|.. ..... .... 
          .. ..... .......|...  ...... . 
          ...  ...... . . |..... ......  
          ..... ......  ..|.. .... ... . 
          .. .... ... . ........ . ... . 
          ..... . ... . ..|......... ... 
          ......... ......|............. 
          ...   ... ......| 
                          |                                    
                          x
, 我們可以看出, 最近的點對要麼在分界線的左邊; 要麼在右邊; 也有可能是一個
點在左邊, 另一個點有右邊. 對分界線的左右兩部分遞迴此過程, 此問題也就可以解決了
. 現在的關鍵問題是, 對於第三種情況, 我們應該怎樣有效地求解呢?
                           |     
          ... .... ...|....|.. .|.... .... 
          .. ..... ...|....|... | ...... . 
          ...  ...... |. . |....|. ......  
          ..... ......|  . |.. .|... ... . 
          .. .... ... |. . .....|. . ... . 
          ..... . ... |.. .|....|..... ... 
          ......... ..|....|....|......... 
          ...   ... ..|....|    |
                           |
                 /____/|/____/     
              mdisLRC| mdisLRC       
                           x
                      /_________/
                         雙道帶
假設mdisLRC是遞迴左右兩子部分遞迴返回的兩個最短距離中的較短者, 諾最短點對出現
在第三種情況中, 這兩個只可能出現在中的雙道帶中. 因此我們可以只檢驗雙道帶中
的點. 為了快速地從雙道帶中選出最短的點對(如果它們比midisLRC還要短), 我們可事先
對這些點相對y進行排序, 從而在選取其中一點後可以快速地判斷另一點是否可能成為選
擇, 使得這對點對最短, 即: 如果這兩個點的y軸距離大於mdisLRC, 則它和排在它後邊的
點都不可能成為最短的點(要記得這些點已經按y進行升序排序過了).
該演算法的時間界為: O(NlogN).

還是來看代碼吧:
double
MinDis( const PointMap M[], int n )
{
    assert( n >= 1 );
    if ( n == 1 )
        return 0.0;

    PointMap *P = malloc( n * sizeof(PointMap) );
    if ( P == NULL )
        Error( "Out Of Space!! " );
    PointMap *Q = malloc( n * sizeof(PointMap) );
    if ( Q == NULL )
        Error( "Out Of Space!! " );

    memcpy( P, M, n * sizeof(M[0]) );
    qsort( P, n, sizeof(P[0]), cmp_x ); /* 遞增快速排序 */
    int i;
    /* 對已按x排序的各個項, 分配一個唯一標記index, 因為有可能存在x座標相等的多
     * 個項 */
    for ( i = 0; i < n; i++ )
        P[i].index = i;
    memcpy( Q, P, n * sizeof(P[0]) );
    qsort( Q, n, sizeof(Q[0]), cmp_y );
   
    double mdis = doMinDis( P, Q, 0, n - 1 );

    free( P );
    free( Q );

    return sqrt( mdis );
}

/*
 * ===  FUNCTION  ===========================================================
 *         Name:  doMinDis
 *  Description:  演算法思想: 分治演算法
 *                 已耗用時間: O(NlogN)
 * ==========================================================================
 */
static double
doMinDis( PointMap P[], PointMap Q[],
        int left, int right )
{
    assert( right - left >= 0 );
    if ( right - left == 0 )
        return MAXDIS;
    else if ( right - left == 1 )
        return Dis( Q[left], Q[right] );
    int i, j, k, mid;
    double mdisleft, mdisright, mdisLRC, tmp;
    mid = ( left + right ) / 2; /* 前提: 以x的升序相吻合, 即index是遞增的. */
    for ( i = left, j = left, k = mid + 1; i <= right; i++ )
        if ( Q[i].index <= mid ) /* 以index=mid作為分界,劃分為兩個部分,劃分 */
            P[j++] = Q[i]; /* + 出來的兩個部分, 各自的項的值按y都是遞增的. */
        else
            P[k++] = Q[i];

    mdisleft = doMinDis( Q, P, left, mid );
    mdisright = doMinDis( Q, P, mid + 1, right );
    mdisLRC = Min( mdisleft, mdisright );

    /* P左右部分分別是對y座標有序的, 將其合并到Q. */
    Merge( Q, P, left, mid + 1, right ); /* 目的: 使得整體相對y是有序的 */
    for ( i = left, k = left; i <= right; ++i ) /* 找出雙道帶中的點 */
        if ( fabs( Q[i].poi_x - Q[mid].poi_x ) < mdisLRC )
            P[k++] = Q[i];
    for ( i = left; i < k; i++ ) /* 各項對y是排序的,大大減少了這裡已耗用時間 */
        for ( j = i + 1; j < k && P[j].poi_y - P[i].poi_y < mdisLRC; j++ )
        /* 對poi_y的測試, 如果y座標已大於mdisLRC, 則點對距離肯定大於mdisLRC */
            if ( (tmp = Dis( P[i], P[j] )) < mdisLRC )
                    mdisLRC = tmp;
    return mdisLRC;
}

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  Merge
 *  Description:  把TmpAs合并到P, TmpAs的左右部分各自相對y是升序的
 * =====================================================================================
 */
static void
Merge( PointMap P[], PointMap TmpAs[], int LeftPos,
        int RightPos, int RightEnd )
{
    int LeftEnd, TmpPos;
    LeftEnd = RightPos - 1;
    TmpPos = LeftPos;

    while ( LeftPos <= LeftEnd && RightPos <= RightEnd )
        if ( TmpAs[LeftPos].poi_y < TmpAs[RightPos].poi_y )
            P[TmpPos++] = TmpAs[LeftPos++];
        else
            P[TmpPos++] = TmpAs[RightPos++];

    while ( LeftPos <= LeftEnd )
        P[TmpPos++] = TmpAs[LeftPos++];
    while ( RightPos <= RightEnd )
        P[TmpPos++] = TmpAs[RightPos++];
}

static double
Min( double lh, double rh )
{
    return lh < rh ? lh : rh;
}

static double
Dis( PointMap po1, PointMap po2 ) /* 返回距離的平方和 */
{
    double xdis = po1.poi_x - po2.poi_x;
    double ydis = po1.poi_y - po2.poi_y;
    return xdis * xdis + ydis * ydis;
}

static int
cmp_x( const void *lh, const void *rh )
{
    double tmp = ((PointMap*)lh)->poi_x
        - ((PointMap*)rh)->poi_x;
    if ( tmp > 0 )
        return 1;
    else if ( fabs( tmp ) < EPS )
        return 0;
    else
        return -1;
}

static int
cmp_y( const void *lh, const void *rh )
{
    double tmp = ((PointMap*)lh)->poi_y
        - ((PointMap*)rh)->poi_y;
    if ( tmp > 0 )
        return 1;
    else if ( fabs( tmp ) < EPS )
        return 0;
    else
        return -1;
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.