分治法计算最短点对

求点集中的最近点对有以下两种方法:
设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)代码描述:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[cpp] view plaincopyprint?
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点之间的距离才会小于δ,最小点对计算才有意义。
    
      对于SL虚框范围内的p点,在SR虚框中与p点距离小于δ的顶多只有六个点,就是图二右图中的2个正方形的6的顶点。这个可以反推证明,如果右边这2个正方形内有7个点与p点距离小于δ,例如q点,则q点与下面正方形的四个顶点距离小于δ,则和δ为SL和SR中的最小点对距离相矛盾。因此对于SL虚框中的p点,不需求出p点和右边虚线框内所有点距离,只需计算SR中与p点y坐标距离最近的6个点,就可以求出最近点对,节省了比较次数。
(否则的话,最坏情形下,在SR虚框中有可能会有n/2个点,对于SL虚框中的p点,每次要比较n/2次,浪费了算法的效率)
     代码描述:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
      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分为左右两部分SxL和SxR,Sy分为SyL和SyR
                    j1=1,j2=1,k1=1,k2=1;
                    mid =Sx.count/2;    //mid为Sx中的中间点点号
                    L =Sx.[mid].x;       //L为Sx中的中间点x坐标
                    for (i=1,i
                    {
                          if (i<=mid)     //将Sx中间线以左地方的点存入到SxL,新数组保持原来的升序性质
                                SxL[k1] =Sx[i]   k1++;
                          else   //将Sx中间线以右的地方的点存入到SxR,新数组保持原来的升序性质
                                SxR.count[k2] = Sx[i]   k2++;
                          if (Sy[i].x //将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,δ);    //获Sx中Sy交界处的最小点位距离,并综合 δL和δR 求出点集的最小点位距离δ
       return δ;
  
       函数merge(SyL,SyR,δ)
       merge(SyL,SyR,δ)
       {
           i1=1,i2=1;
           for (i=1,i //获取SyL中在左边虚框(距离小于δ)内的点,存入到S'yL中,新数组保持原来的升序性质
           {
               if (SyL[i].x>L-δ)
                   then S'yL[i1]= SyL[i], i1++,
            }
           for (i=1,i //获取SyR中在右边虚框(距离小于δ)内的点,存入到S'yR中,新数组保持原来的升序性质
           {
               if (SyR[i].x
               then S'yR[i2]= SyR[i], i2++,
           }
  
           t=1;
           for (i=1,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)要高效。

你可能感兴趣的:(算法设计)