最近点对问题

最近点对问题の目录

  • 最近点对问题
    • 什么是最近点对问题
    • 代码基本框架
    • 蛮力法及其代码
    • 分治法及其代码
    • 一般分治法遇到的特殊情况
    • 如何优化分治法(6点确定与4点确定解法)
    • 优化后分治法(4点确定代码)

什么是最近点对问题

算法第二次实验是求解最近点对问题 同时对算法进行分析,这里仅对解决二维最近点对问题做探讨
首先就是最近点对问题的引入
最近点对是指在平面/直线上分布许多点,同时这些点不重复,其中最近的一对点就是最近点对。
二维情况下的问题
如果一维情况下的点如果取一中心线分隔开求解左右最小的d那么左右至多只能存在一个点
但二维则不同,在左右最小d求出来后仍然可能存在许多点 又该如何求解呢?

代码基本框架

首先是预定义点的类或者结构体用以存放数据,Double类型的数据变量存放x,y.


class Point
{double x,y;public:Point(){x=0;y=0;}Point(double a,double b){x=a;y=b; //构造函数    }double GetPoint_X(){return x; //返回点的x坐标 } double GetPoint_Y(){return y;    //返回点的y坐标 } ~Point(){}void SetPoint(double a,double b){x=a;y=b;   //设置点坐标 }
};
void CopyPoint(Point A[],Point B[],int m,int n)
{for(int i=0;m<=n;i++,m++)A[i].SetPoint(B[m].GetPoint_X(),B[m].GetPoint_Y());
}
double ForceMethod(Point arr[],int Num)
{//蛮力法
}
double Merge(Point arr[],double Mindis1,int Num)
{//递归调用的合并函数
}
double Dichotomy(Point arr[],int Low,int High)
{//递归的分解函数
}
int main()
{//main 函数
}

蛮力法及其代码

对于这类问题,最容易想到的解法就是蛮力法直接暴力查解,代码实现简单易懂。
整体思路对于每一个点都和其他的N-1个点都进行一次比较,记录最小距离的点对即可。

double ForceMethod(Point arr[],int Num)
{//蛮力求解算法 double Mindis=999999; //存放最小距离 double Minx[2],Miny[2]; //存放最小距离两个点坐标 for(int i=0;i<Num;i++)for(int j=i+1;j<Num;j++){double dis=GetDis(arr[i],arr[j]);if(Mindis>dis){Mindis=dis;Minx[0]=arr[i].GetPoint_X();Minx[1]=arr[j].GetPoint_X();Miny[0]=arr[i].GetPoint_Y();Miny[1]=arr[j].GetPoint_Y(); }}
cout<<"最近点对为:"<<"("<<Minx[0]<<","<<Miny[0]<<")与("<<Minx[1]<<","<<Miny[1]    <<")"<<endl;
cout<<"它们的距离为:"<< Mindis<<endl;
cout<<endl;
return Mindis;
}

分治法及其代码

分治法相比于蛮力法就复杂许多了,首先需要思考何为分治?
其实算法的整体思路比较像合并排序算法
分为两个部分:
1.分解为左右子问题
对于分解 重点在于从哪分解分到什么情况结束
首先是从哪分解?

为了使得左右部分的点数目尽量均匀,我们首先需要对所有的点按x的大小进行一次排序
这样我们选取的Mid点就可以作为左右的分隔线
注:这里我选择的Mid当点数为偶数时中间有两个点选择前面那
这样得到的图应该是有一个中点在分隔线上 如下图所示
在计算的时候不要忘记把这个点放到左边的点对内进行计算。

分解到什么时候结束呢?
当只有两个点 或者三个点 时已经可以直接求出最小点对的距离了
此时自然就可以结束分解了,得出分解到最小时候的左/右的最近点对距离

这里首先给出分解与递归的代码:
Merge为合并函数 在下面来讲

//需要新建左右部分的点数组实现左右分别递归
double Dichotomy(Point arr[],int Low,int High)
{int Num=High-Low; //Num=点数 if(Num==1)return 9999999; //一个点时不用求了相当于无穷大if(Num==2) //只有两个点 return GetDis(arr[0],arr[1]);if(Num==3) //三个点 return min(GetDis(arr[0],arr[1]),min(GetDis(arr[0],arr[2]),GetDis(arr[1],arr[2])));//其他情况 int Mid=Num%2==0?Num/2-1:Num/2;Point *Ltemp=new Point[Mid+1];Point *Rtemp=new Point[High-Mid-1];CopyPoint(Ltemp,arr,Low,Mid);CopyPoint(Rtemp,arr,Mid+1,High-1);double dis1=Dichotomy(Ltemp,0,Mid+1); //递归调用求左 double dis2=Dichotomy(Rtemp,0,High-Mid-1); //求右 double Mindis1=min(dis1,dis2); //非跨越最小return Merge(arr,Mindis1,Num);
}

2.合并左右子问题
合并左右是代码最关键的一个部分,因为我们需要去计算跨越中线的最小Dis
首先我们需要排除一些不可能的点来减少运算量

1.首先排除X方向上不可能的点
举个栗子 如下图所示
已知dl与dr是左右的最小距离
那么d=min(dl,dr).
此时我们以L为中线 L-d,L+d 为边界 可以得到一个范围
超过这个X范围的点 都不可能是最小跨越距离------因为它不可能小于d了。
这样在合并的时候就可以剔除左右超过X范围的点了,
2.求解跨越最小
然后我们分别比对范围内左边与右边的每一个点 就可以找到跨越的最小距离d2
如果d2<d了.
那么最小距离就是d2了~ 大功告成!

这里给出常规合并代码 如下

double Merge(Point arr[],double Mindis1,int Num)
{/*注:该部分为分治算法合并部分合并左右 计算跨越左右的最小值 与传入的Mindis1 做比较变量含义: 1.Mindis1:非跨越的最小点对距离 .2.Num:左右整体的数组长度也就是待合并点个数 .3.Mid:中间的点 靠左.4.leftnum, rightnum: 左右符合 Mid+-Mindis1范围内的点的数量. 5.Leftarr[], Rightarr[] 左右数组 ,存放符合范围内的点的值. */ int Mid=Num%2==0?Num/2-1:Num/2;int leftnum=0,rightnum=0; //记录点数量 for(int i=0;i<=Mid;i++){//计算left点的数量 double len;len=arr[i].GetPoint_X()-arr[Mid].GetPoint_X();if(len<=0&&len> -Mindis1)leftnum++;}for(int i=Mid+1;i<Num;i++){//计算Right点的数量 double len;len=arr[i].GetPoint_X()-arr[Mid].GetPoint_X();if(len>=0&&len<Mindis1)rightnum++;    }//新建左右arr对象数组 并赋值 Point *Leftarr=new Point[leftnum];Point *Rightarr=new Point[rightnum];int Lflag=0,Rflag=0;for(int i=0;i<=Mid;i++){    double len;len=arr[i].GetPoint_X()-arr[Mid].GetPoint_X();if(len<=0&&len> -Mindis1)Leftarr[Lflag++].SetPoint(arr[i].GetPoint_X(),arr[i].GetPoint_Y());     } for(int i=Mid+1;i<Num;i++){double len;len=arr[i].GetPoint_X()-arr[Mid].GetPoint_X();if(len>=0&&len<Mindis1)Rightarr[Rflag++].SetPoint(arr[i].GetPoint_X(),arr[i].GetPoint_Y());}//常规解法 double Mindis2=Mindis1;for(int i=0;i<leftnum;i++)for(int j=0;j<rightnum;j++){double Dis=GetDis(Leftarr[i],Rightarr[j]);if(Dis< Mindis2) Mindis2=Dis;}return  Mindis2;

一般分治法遇到的特殊情况

我们把重点放在如何 画X范围 求解Mindis2 就会发现问题
仔细观察这张图和这一小段代码

     //常规解法 double Mindis2=Mindis1;for(int i=0;i<leftnum;i++)for(int j=0;j<rightnum;j++){double Dis=GetDis(Leftarr[i],Rightarr[j]);if(Dis< Mindis2) Mindis2=Dis;}

很明显 存在一种特殊情况:当左右两侧的点是这样分布的时候 (自己瞎画的 灵魂画手哈哈哈)

当左右的点都在一条直线上时,并不能通过X范围来有效的排除点
而最终求解Dis2是双重for循环嵌套
这样的情况下该算法其实还是一种N^2的求解办法

如何优化分治法(6点确定与4点确定解法)

1.当一个左点确定时,右边相应的最多只用算6个点。
当某左点确定时,我们可以首先得出一个模糊的范围
这个范围大小很明显是一个矩形(从某左点往右划d的范围,再往上下各画一个d的矩形)
如下图b所示

在这样一个范围内最多有多少个点呢?我们通过一个限制条件d先来考虑一下
d的含义是左右的最短点对距离:也就是说不存在非跨越的比这个还短的点对距离
我们把这个图分为6块 如下图所示

很明显的最多的情况应该是每一块至多有一个点
这样才满足不大于d的条件

如果有7个点那么必然在某一块中会出现两个点
而某一块中最长的斜边d1只有5/6d那么长(勾股定理,如上上图第1块所示)
所以范围内必然不能存在6个以上的点,我们只需要找到范围上界或者下界运算6个点(如果有6个的话)就可以得出结果了。
这就是6点确定的由来

2.再优化,其实最多只用算4个
接下来简单说一下4点确定,其实我也没太搞懂极限情况的极值由来
这一部分是根据论文:求平面点集最近点对的一个改进算法-1998年 周玉林 熊鹏荣 朱洪
具体论文内容可以百度查阅 这里仅仅放出部分

这里可以看出 若范围2中存在点 它就要比pq点对的距离要小,所以只需要验证上/下方向上的两个点就可以得出最小的点了.若2有点 那就是比对2,3分别与p的距离。若2无点那就是比对1,3分别与p的距离。
综合成一句话 就是比对Y坐标最近的四个点(如果存在4个点),如图7所示
对上面两个点和下面两个点比较就可以得出结果。
所以我们首先就要考虑对右边点按Y升序排列,然后找到这四个点分别比较。

优化后分治法(4点确定代码)

该部分替换原先常规算法那部分

   //优化算法 只用考虑四个点计算//思路参考论文:求平面点集最近点对的一个改进算法-1998年 周玉林 熊鹏荣 朱洪 sort(Rightarr,Rightarr+rightnum,Cmp_Y);    double Mindis2=Mindis1;for(int i=0;i<leftnum;i++){int temp=0; //标记点 for(int j=0;j<rightnum;j++){if(Leftarr[i].GetPoint_Y()>=Rightarr[j].GetPoint_Y())temp++;}//cout<<"temp:"<<temp<<endl; //只考虑上下各两个  temp+1,temp, temp-1,temp-2;for(int k=0;k<2&&temp+k<rightnum;k++){if(GetDis(Leftarr[i],Rightarr[k+temp])<=Mindis2)Mindis2=GetDis(Leftarr[i],Rightarr[k+temp]);}for(int l=1;l<=2&&temp-l>=0;l++){if(GetDis(Leftarr[i],Rightarr[temp-l])<=Mindis2)Mindis2=GetDis(Leftarr[i],Rightarr[temp-l]);}}return Mindis2;

结束线------------------------------------------------------

蛮力法与分治法解决最近点对问题-详细分析与C++代码实现相关推荐

  1. 分治法解决最近点对问题

    问题 给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小.严格地说,最接近点对可能多于1对.为了简单起见,这里只限于找其中的一对. 原理(这段为抄袭https://blog. ...

  2. 深大算法设计与分析实验二——分治法求最近点对问题

    源代码: 深大算法设计与分析实验二--分治法求最近点对问题代码-C/C++文档类资源-CSDN下载 目录 实验问题 一.实验目的: 二.内容: 三.算法思想提示 产生不重复的随机点算法: 蛮力算法: ...

  3. 算法设计与分析 实验二 分治法求解最近点对问题

    分治法求解最近点对问题 一.实验目的与要求 1.实验基本要求 2.实验亮点 二.实验内容与方法 三.实验步骤与过程 (一)一些准备工作 1.实验流程 2.数据生成与去除重复点 (二)暴力穷举法 1.算 ...

  4. Algorithm:C++语言实现之求最大连续子数组(暴力法、分治法、分析法、动态规划法)

    Algorithm:C++语言实现之求最大连续子数组(暴力法.分治法.分析法.动态规划法) 目录 求最大连续子数组 T1.code暴力法  O(n3) T2.分治法   O( n*log(n) ) T ...

  5. 算法总结系列(二):贪心法、分治法

    算法2--贪心法.分治法 贪心法 币种统计问题 分治法 快速排序 贪心法 关键词:局部最优.近似 算法描述: 1)定义初始点 2)从初始点出发,找当前点局部最优解 3)以局部最优解为初始点,找新的局部 ...

  6. 分治法解决最大子数组问题

    分治法解决最大子数组问题 参考文章: (1)分治法解决最大子数组问题 (2)https://www.cnblogs.com/Christal-R/p/Christal_R.html (3)https: ...

  7. 分治法解决组合总和问题(leetcode216)

    nums数组中元素是正整数 大问题转换为小问题 思路和分治法解决组合相同,代码也相似 分治法解决组合问题(递归)_m0_52043808的博客-CSDN博客 只不过递归出口时需要判断组合总和是否为n ...

  8. 分治法解决循环赛日程表

    分治法解决循环赛日程表 问题描述 设有n=2^k个运动员要进行羽毛球循环赛,现要设计一个满足以下要求的比赛日程表: (1)每个选手必须与其他n-1个选手各赛一次. (2)每个选手一天只能比赛一次. ( ...

  9. 分治法解决矩阵乘法问题

    分治法解决矩阵乘法问题 传统for循环: #include<iostream> #include<cstdio> #include <vector> #includ ...

最新文章

  1. CS231n 学习笔记(2)——神经网络 part2 :Softmax classifier
  2. 美团--最小唯一前缀
  3. AWK再次认识--内置的参数,以及编写脚本
  4. 所以進入到二十一世紀之後
  5. 电脑如何测网速_【网络调试】网络速度检测工具有哪些?这五款工具让你随时掌控网速...
  6. 【 58沈剑 架构师之路】4种事务的隔离级别,InnoDB如何巧妙实现?
  7. python读取txt矩阵数据(转换为excel格式利用pandas读取)
  8. opera官方教程 预订一
  9. 基于Springboot的个人健康监控管理系统
  10. win7怎么看计算机Mac地址,Win7怎么查看MAC地址?Win7查看MAC地址的两种方法
  11. 戴尔电脑风扇声音大的解决方法
  12. 项目是如何死掉的?太过真实!哈哈哈~嗝~~
  13. html微课制作软件,炫页网.多彩炫页工具-微课大赛指定HTML5课程制作工具
  14. 陈艾盐:春燕百集访谈节目第二十六集
  15. 太原理工大于丹计算机,太原理工大学硕士生将参加中国第30次南极考察
  16. 一般人看不透!沉默的大多数用户才是产品发展的关键!
  17. C - Super Mario(分块+二分)
  18. DELPHI线程创建与使用
  19. [python爬虫之路dya3]: requests库的基本使用
  20. phpmailer报 You must provide at least one recipient email address.解决办法

热门文章

  1. JS脚本调起小狐狸MetaMask浏览器扩展插件解锁
  2. python_for_hrm:基于腾讯企业邮箱SMTP服务发送员工工资条(竖表)
  3. python搭建web网站 腾讯云,python搭建web网站代码
  4. 【RabbitMQ】安装、启动、配置、测试一条龙
  5. 爱奇艺视频wasm转js分析,cmd5x算法脱离环境限制
  6. 适合0基础学习Python的路线图
  7. 数据结构与算法学习(第三天单链表节点数、倒数k个节点值、反转、尾到头打印)
  8. JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(1)
  9. Excel·VBA单元格区域按颜色求和等计算
  10. 用Python来P图,分析怎么吃鸡?只有想不到,没有做不到