• 编程之美2.11:寻找最近的点对


    求点集中的最近点对有以下两种方法:

    设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坐标进行升序排序,获得点集point[] array

         2)令δ=∞;   //δ为最大点位距离

         3)Divide_conquer(point[] array,left,right)  //分治法

                 if (left == right) then δ=∞;    //如果point[]中只有一个点,则δ=

                      return δ;

                 else if (right - left ==1) // point[]中只有2个点,则直接求这两个点的距离

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

                       return δ;

                 else    //如果point[]中多于2个点,则将point[]分治,以中心点画线,将point[]分为左右两部分AB

                      mid = (LeftIndex + rightIndex)>>1;   //mid为当前段中的中间点index           

          double d1 = Closest_Pair(leftIndex,mid);                  

          double d2 = Closest_Pair(mid +1,rightIndex); 

                      d  =min(d1,d2);  

            list<point> leftList = new <point>();

            list<point> lrightList = new <point>();

                // 分离出宽度为d,距point[mid]<=d的区间,其实也就是化了两条平行于x= point[mid].x的竖线。(之后还需要根据左边A的p,在右边选距离p.y<=d,在化两条横线,这样一个动态的矩形也就化出来了。(矩形包含两个正方形,一条边重合,也就是最多存在6(顶)点(鸽巢原理),可能满足条件     (如上面的图figure2)

    循环遍历 数组

      1. if (fabs(point[mid].x -point[i].x) <=d && i <= mid)

         leftList.add(point[i] //左边符合条件的点

      2. if (fabs(point[mid].x -point[i].x) <=d && i > mid)

          rightList.add(point[i]) //右边符合条件的点

       

    // 线性扫描

      foreach ( point leftPoint in leftList)

      {

      foreach(point rightPoint in rightList)

                 d3=dis(leftPoint, rihgtPoint); //求左边点和右边点的最小值

                if (d >d3)

        {

                   d =d3; //更新最小值
        }

    }  

     return d;       

     

    详细的代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication4
    {
        public class Point
        {
    
            public Point(double a, double b)
            {
                X = a;
                Y = b;
            }
            double x;
    
            public double X
            {
                get { return x; }
                set { x = value; }
            }
    
            double y;
    
            public double Y
            {
                get { return y; }
                set { y = value; }
            }
    
            public static void Copy (Point a, Point b)
            {            
                a.X = b.X;
                a.Y = b.Y;
            }
    
            bool CompyX(Point a, Point b)
            {
                if (a.X != b.X)
                {
                    return a.x < a.y;
                }
    
                return a.y < b.y;
            }
    
            public static double Dis(Point a, Point b)
            {
                return Math.Sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
            }
        }
    
        public static class SortHelper
        {
            public static void MergeSortByx(Point[] array)
            {
                int increaseMent = 1;
    
                while (increaseMent < array.Length)
                {
                    MergeByx(array, increaseMent);
                    increaseMent <<= 1;
                }
            }    
    
            public static void MergeByx(Point[] array, int increaseMent)
            {
                Point[] tempArray = new Point[array.Length];
    
                for (int t = 0; t < tempArray.Length; t++)
                {
                    tempArray[t] = new Point(0, 0); // 初始化
                }
    
                int lastIndex = array.Length - 1;
    
                int l1 = 0;   //第1个有序表的起始位置
                int h1 = 0;   //第1个有序表的结束位置
                int l2 = 0;   //第2个有序表的起始位置
                int h2 = 0;   //第2个有序表的结束位置
    
                int m = 0;   //临时表的初始位置
    
    
                // 注意这里的临界条件(l2要存在,L2的index是: l1 + increaseMent<=lastIndex)
                while (l1 + increaseMent <= lastIndex)
                {
                    l2 = l1 + increaseMent;
                    h1 = l2 - 1;
                    h2 = l2 + increaseMent - 1 < lastIndex ? l2 + increaseMent - 1 : lastIndex;
    
                    int i = l1;
                    int j = l2;
    
                    //两个有序表中的记录没有排序完
                    while (i <= h1 && j <= h2)
                    {
                        //第1个有序表记录的关键码小于第2个有序表记录的关键码
                        if (array[i].X < array[j].X)
                        {
    
                            Point.Copy(tempArray[m], array[i]);
                            m++;
                            i++;                        
                        }
                        else //第2个有序表记录的关键码小于第1个有序表记录的关键码
                        {
                            Point.Copy(tempArray[m], array[j]);
                            m++;
                            j++; 
                        }
                    }
    
                    //第1个有序表中还有记录没有排序完
                    while (i <= h1)
                    {
                        Point.Copy(tempArray[m], array[i]);
                        m++;
                        i++; 
                    }
    
                    //第2个有序表中还有记录没有排序完
                    while (j <= h2)
                    {
                        Point.Copy(tempArray[m], array[j]);
                        m++;
                        j++; 
                    }
    
                    l1 = h2 + 1;
                }
    
                //原顺序表中还有记录没有排序完
                while (l1 <= lastIndex)
                {
                    Point.Copy(tempArray[m], array[l1]);
                    m++;
                    l1++; 
                }
    
                //临时顺序表中的记录复制到原顺序表,使原顺序表中的记录有序
                for (int i = 0; i < array.Length; i++)
                {
                    Point.Copy(tempArray[i], array[i]);
                    m++;
                    i++; 
                }
            }       
        }
    
        public class CalculateHelper
        {
            public double GetClosetDistant(Point[] array)
            {
               return GetClosetDistant(array, 0, array.Length - 1);
            }
    
            public double GetClosetDistant(Point[] array, int leftIndex, int rightIndex)
            {
                double distant = double.MaxValue;
    
                //情况一: 当前区域只有一个点时,返回最大值,即当前值无效 (出口一)
                if (leftIndex == rightIndex)
                {
                    return distant;
                }
    
                // 情况二:当前区域只有两个点时,直接返回这两个点的距离 (出口二)
                if (leftIndex + 1 == rightIndex)
                {
                   return  Point.Dis(array[leftIndex], array[rightIndex]);
                }
    
                // 按照x排序
                SortHelper.MergeSortByx(array);
    
                // 情况三,当前区域点的个数大于2的时候,需要采用分治法,把当前区域分成左右两个部分,直到满足情况一或二
    
                int midIndex = (leftIndex + rightIndex) >> 1;
                double leftDistance = GetClosetDistant(array, leftIndex, midIndex);
                double rightDistance = GetClosetDistant(array, midIndex + 1, rightIndex);
    
                distant = Math.Min(leftDistance, rightDistance); //求出左右两边区域的最小距离
    
                if (distant < 0.43)
                {
                    distant = distant;
                }
    
                for (int i = leftIndex; i <= midIndex; i++) //遍历左边的点
                {
                    if (Math.Abs(array[i].X- array[midIndex].X) <distant) //选出左边区域距离x = array[midIndex].x <d的点 (画左边的1条竖线)                  
                    {
                        for (int j = midIndex + 1; j <= rightIndex; j++) //遍历右边的点
                        {
                            if (Math.Abs(array[i].X - array[midIndex].X) < distant) //选出左边区域距离x = array[midIndex].x <d的点 (画右边的1条竖线)
    
                                if (Math.Abs(array[i].Y - array[midIndex].Y) < distant) // 画出两条平行于 y= array[midIndex].Y的两条横线,到这一步时,
                                    //矩形区域已经画好了,符合条件的点最多有6个
                            {
                                double bothDistance = Point.Dis(array[i], array[j]); //计算左右点的距离
    
                                if (bothDistance < distant)
                                {
                                    distant = bothDistance; // 更新最小距离
                                }
    
                                if (distant < 0.43)
                                {
                                    distant = distant;
                                }
                            }
                        }
                    }
                }
    
                if (distant < 0.43)
                {
                    distant = distant;
                }
                return distant;
            }
        }
    }
    
    // 主函数 static void Main(string[] args) { CalculateHelper helper = new CalculateHelper(); Point[] array = new Point[14]; array[0] = new Point(2, 2); array[1] = new Point(0.5, 0.5); array[2] = new Point(0.25, 1); array[3] = new Point(1, 2); array[4] = new Point(3, 1); array[5] = new Point(2, 0.7); array[6] = new Point(1, 1); array[7] = new Point(0.6, 0.8); array[8] = new Point(0.9, 0.5); array[9] = new Point(2, 1); array[10] = new Point(4, 2); array[11] = new Point(1.1, 0.5); array[12] = new Point(8, 0.5); array[13] = new Point(0.7, 2); double dintance = helper.GetClosetDistant(array); }

     

  • 相关阅读:
    [Java] Hibernate
    python基础(十三):函数(一)公共操作
    python基础(十二):数据结构(五)集合
    python基础(十二):数据结构(四)字典
    python基础(十一):数据结构(三)元组
    python基础(十):数据结构(二)列表
    python基础(八):流程控制(五)循环
    python基础(七):流程控制(一)if
    python基础(六):运算符
    python基础(五):转换数据类型
  • 原文地址:https://www.cnblogs.com/Jessy/p/3512076.html
Copyright © 2020-2023  润新知