1. 程式人生 > >平面上求最近點對問題

平面上求最近點對問題

 求點集中的最近點對有以下兩種方法:

設p1=(x1, y1), p2=(x2, y2), …, pn=(xn, yn)是平面上n個點構成的集合S,設計演算法找出集合S中距離最近的點對。

1、蠻力法(適用於點的數目比較小的情況下)

     1)演算法描述:已知集合S中有n個點,一共可以組成n(n-1)/2對點對,蠻力法就是對這n(n-1)/2對點對逐對進行距離計算,通過迴圈求得點集中的最近點對:

     2)程式碼描述:

double MinDistance = double.maxvalue;  //設定一個MinDistance儲存最近點對的距離,初始值為無窮大
int PointIndex1,PointIndex2; //設定PointIndex1,PointIndex2分別儲存最近點對的兩個點編號
for (i=1; i< n; i++)    //迴圈計算n(n-1)/2對點對的距離
{
     for (j=i+1; j<=n; j++)
     {
           double PointDistance = Distance(S[i],S[j]);   //求得point i和point j之間的距離
            if PointDistance < MinDistance;  //如果當前點對距離小於最小點對距離,則設定最小點對距離等於當前點對距離
           {
                 MinDistance = PointDistance;
                 PointIndex1 = i;
                 PointIndex2 = j;
            }
       }
 }

  3)演算法時間複雜度:演算法一共要執行 n(n-1)/2次迴圈,因此演算法複雜度為O(n2)

2、分治法

     1)演算法描述:已知集合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次,浪費了演算法的效率

     程式碼描述:

     1)對點集S的點x座標和y座標進行升序排序,獲得點集Sx和Sy

     2)令δ=∞;  //δ為最小點位距離

     3)Divide_conquer(Sx,Syδ) //分治法

             if (Sx.count=1) thenδ=∞;   //如果Sx中只有一個點,則δ=

                  returnδ;

             else if(Sx.count=2 and d(Sx.[0],Sx.[1])<δ//如果Sx中只有2個點,則δ為兩點之間距離

                  δ=d(Sx.[0], Sx.[1]); 

                   return δ;

             else   //如果Sx中多於2個點,則Sx,Sy分治,以中心點畫線,將Sx分為左右兩部分SxLSxRSy分為SyLSyR

                   j1=1,j2=1,k1=1,k2=1;

                   mid =Sx.count/2;  //midSx中的中間點點號

                   L =Sx.[mid].x;     //LSx中的中間點x座標

                   for(i=1,i<Sx.count,i++)

                   {

                         if(i<=mid)   //將Sx中間線以左地方的點存入到SxL,新陣列保持原來的升序性質

                               SxL[k1] =Sx[i]   k1++;

                         else  //將Sx中間線以右的地方的點存入到SxR陣列保持原來的升序性質

                               SxR.count[k2] = Sx[i]   k2++;

                         if(Sy[i].x<L)   //將Sy中間線以左地方的點存入到SyL陣列保持原來的升序性質

                                SyL[j1] = Sx[i]   j1++;

                         else  //將Sy中間線以右地方的點存入到SyR陣列保持原來的升序性質

                               SyR[j2] = Sx[i]   j2++;

                   }

             δL = Divide_conquer(SxL,SyLδ);   //獲取Sx中的的最小點位距離δL

             δR = Divide_conquer(SxR,SyRδ);  //獲取Sy中的的最小點位距離δR

             δ= min (δL,δR);

             δ=merge(SyL,SyRδ);  //獲SxSy交界處的最小點位距離,並綜合 δLδR 求出點集的最小點位距離δ

      returnδ;

      函式merge(SyL,SyRδ)

      merge(SyL,SyRδ)

      {

          i1=1,i2=1;

          for(i=1,i<SyL.count,i++)  //獲取SyL中在左邊虛框(距離小於δ)內的點,存入到S'yL陣列保持原來的升序性質

          {

              if(SyL[i].x>L-δ)

                  then S'yL[i1]= SyL[i], i1++,

           }

          for(i=1,i<SyR.count,i++)  //獲取SyR中在右邊虛框(距離小於δ)內的點,存入到S'yR陣列保持原來的升序性質

          {

              if(SyR[i].x<L+δ)

              then S'yR[i2]= SyR[i], i2++,

          }

          t=1;

          for(i=1,i<S'yL.count,i++)

           {

                while(S'yR[t].y< S'yL[t].y and t < SyR.count) //獲取點集S'yR內距離點S'yL[t]y座標最接近的點號

                { t++; }

                for( j= max(1,t-3), j<=min(t+3,S'yR.count),j++)   //計算S'yR中的點與S'yL[t]y座標相鄰的六個點的距離

                {

                      if(d(S'yL[i],S'yL[j])<δ)   //如果前兩點之間距離小於δ

                      then δ = d(S'yL[i],S'yL[j]);  //則最小點位距離δ為當前兩點之間距離

                }

          return δ

      }

 3)演算法時間複雜度:

      首先對點集S的點x座標和y座標進行升序排序,需要迴圈2nlogn次,複雜度為O(2nlogn)

      接下來在分治過程中,對於每個S'yL中的點,都需要與S'yR中的6個點進行比較

            O(n)= 2O(n/2) + (n/2)*6  (一個點集劃分為左右兩個點集,時間複雜度為左右兩個點集加上中間區域運算之和)

      其解為O(n)< O(3nlogn)

     因此總的時間複雜度為O(3nlogn),比蠻力法的O(n2)要高效。