分治法求最近点对问题

分治法

     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)要高效。

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是C语言分治法最近问题的代码: #include <stdio.h> #include <stdlib.h> #include <math.h> #define INF 1e20 //定义无穷大 //定义的结构体 typedef struct { double x; double y; } Point; //比较函数,按x坐标排序 int cmp_x(const void *a, const void *b) { Point *p1 = (Point *)a; Point *p2 = (Point *)b; if (p1->x < p2->x) return -1; else if (p1->x > p2->x) return 1; else return 0; } //比较函数,按y坐标排序 int cmp_y(const void *a, const void *b) { Point *p1 = (Point *)a; Point *p2 = (Point *)b; if (p1->y < p2->y) return -1; else if (p1->y > p2->y) return 1; else return 0; } //计算两之间的距离 double dist(Point p1, Point p2) { return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y)); } //暴力最近对 double brute_force(Point *p, int n) { double min_dist = INF; for (int i = 0; i < n-1; i++) { for (int j = i+1; j < n; j++) { double d = dist(p[i], p[j]); if (d < min_dist) min_dist = d; } } return min_dist; } //合并最近对 double merge(Point *p, int n, double d) { double mid = p[n/2].x; //中线 Point *strip = (Point *)malloc(n * sizeof(Point)); int j = 0; for (int i = 0; i < n; i++) { if (fabs(p[i].x - mid) < d) strip[j++] = p[i]; } qsort(strip, j, sizeof(Point), cmp_y); //按y坐标排序 double min_dist = d; for (int i = 0; i < j-1; i++) { for (int k = i+1; k < j && strip[k].y-strip[i].y < min_dist; k++) { double d = dist(strip[i], strip[k]); if (d < min_dist) min_dist = d; } } free(strip); return min_dist; } //分治最近对 double closest_pair(Point *p, int n) { if (n <= 3) return brute_force(p, n); int mid = n/2; double dl = closest_pair(p, mid); //左半边最近对距离 double dr = closest_pair(p+mid, n-mid); //右半边最近对距离 double d = fmin(dl, dr); //取左右半边最近对距离的较小值 return merge(p, n, d); //合并最近对 } int main() { int n; printf("请输入的数量:"); scanf("%d", &n); Point *p = (Point *)malloc(n * sizeof(Point)); printf("请输入%d个的坐标:\n", n); for (int i = 0; i < n; i++) { scanf("%lf%lf", &p[i].x, &p[i].y); } qsort(p, n, sizeof(Point), cmp_x); //按x坐标排序 double ans = closest_pair(p, n); printf("最近对的距离为:%lf\n", ans); free(p); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值