目录
实验问题
一、实验目的:
- 掌握分治法思想。
- 学会最近点对问题求解方法。
二、内容:
1. 对于平面上给定的N个点,给出所有点对的最短距离,即,输入是平面上的N个点,输出是N点中具有最短距离的两点。
2. 要求随机生成N个点的平面坐标,应用蛮力法编程计算出所有点对的最短距离。
3. 要求随机生成N个点的平面坐标,应用分治法编程计算出所有点对的最短距离。
4. 分别对N=100000—1000000,统计算法运行时间,比较理论效率与实测效率的差异,同时对蛮力法和分治法的算法效率进行分析和比较。
5. 如果能将算法执行过程利用图形界面输出,可获加分。
三、算法思想提示
1. 预处理:根据输入点集S中的x轴和y轴坐标进行排序,得到X和Y,很显然此时X和Y中的点就是S中的点。
2. 点数较少时的情形

 3.  点数|S|>3时,将平面点集S分割成为大小大致相等的两个子集SL和SR,选取一个垂直线L作为分割直线,如何以最快的方法尽可能均匀平分?注意这个操作如果达到θ(n2) 效率,将导致整个算法效率达到θ(n2)
效率,将导致整个算法效率达到θ(n2) 。
。

4. 两个递归调用,分别求出SL和SR中的最短距离为dl和dr。
5. 取d=min(dl, dr),在直线L两边分别扩展d,得到边界区域Y,Y’是区域Y中的点按照y坐标值排序后得到的点集(为什么要排序?),Y'又可分为左右两个集合Y’L和Y’R

6. 对于Y’L中的每一点,检查Y’R中的点与它的距离,更新所获得的最近距离,注意这个步骤的算法效率,请务必做到线性效率,并在实验报告中详细解释为什么能做到线性效率?

方法:蛮力法、分治法。
产生不重复的随机点算法:
首先能想到的是产生一个点就与前面产生的点一一比较看是否重复,但是当数据量十分庞大的时候需要很多的时间。既然这样时间复杂度会很大,那么我就用增大空间复杂度来减小时间复杂度。由于C++语言中的rand函数最大产生的数字不会超过40000,于是我开了一个大小为40000×40000的布尔型数组用于记录点是否被使用。首先将这个二维数组的所有值设位false代表没有被设定,若产生一个点且之前没有产生过,则将产生点的x,y坐标作为布尔型二维数组的下标,将对应的这个数值改为true,如果之前有产生过,则重新获取一个数。
伪代码如下:
Creat_Random_Point (P,n)
		for i=0 to 40000
			for j=0 to 40000
				Prevent[i][j]=false
		for i=0 to n
			xtemp=rand   ytemp=rand
			if Prevent[xtemp][ytemp]==true
				i--
			else
				P[i].set(xtemp,ytemp)
				Prevent[xtemp][ytemp]=true	
蛮力算法:
蛮力法就是将平面上的所有点对进行一一计算距离,并与最短的哪个距离依次进行比较,找到那个最短的距离。
伪代码如下:
Slow_FindPoint (P, n, Min)
Min=Distance(P[0],P[1])
for i=1 to n-1
		for j=i+1 to n
			if  Distance(P[i],P[j])<Min
				Min= Distance(P[i],P[j])
我们可以看到这里面有两个for循环而且一个到n-1一个到n,所以这个时间复杂度是 的。
的。
分治算法
分治法可以将问题缩小化,将庞大的问题逐渐缩小成单个小的问题进行解决。
         在寻找点当中,先将点集按照x轴方向排序,我们将使用快速排序进行排序,这个时间效率是 。
。
接着将问题分治,利用递归来进行分治。找到x轴的中轴线将点一分为二,同理得到的两个区域也向上述描述一样一分为二,直到分到只有一个点或者两个点。

当只有一个点时,将最小的值设置为无穷大,若是只有两个点,则将最小值设 为这两个点的距离。
接下来进行合并操作。
伪代码如下:
接下来是需要考虑合并算法,我们需要将算法尽量做到线性。
                方法1:分治完毕后我们     得到了两侧最小的距离 ,先比较
,先比较 哪一个   更小,并记为
哪一个   更小,并记为 。因为不一定这个距离
。因为不一定这个距离 是最短的距离,有可能在分界处两端的点的    距离是最短的,所以我们需要考虑到这一部分。但是我们也不能让这两个点依次比    较,因为这样算法效率是
是最短的距离,有可能在分界处两端的点的    距离是最短的,所以我们需要考虑到这一部分。但是我们也不能让这两个点依次比    较,因为这样算法效率是 的,所以需要缩小遍历的数量。
的,所以需要缩小遍历的数量。
                以两侧合并的中线为基点,分别向两端扩展 的距离,在这个区域之内的点的两点距离是有可能比
的距离,在这个区域之内的点的两点距离是有可能比 更小的,如果将这两点依次比较,如果数据比较极端的话也会将算法效率降低到
更小的,如果将这两点依次比较,如果数据比较极端的话也会将算法效率降低到 ,所以还要加以限制。
,所以还要加以限制。
                于是在比较每一个点的时候,将y轴方向距离小于 的点进行,大于
的点进行,大于 就不用比较了,我们可以证明这样一个点比较的数量不大于5个。且范围内的点最多只有6个。
就不用比较了,我们可以证明这样一个点比较的数量不大于5个。且范围内的点最多只有6个。

        如上图,我们取极端的情况,若两个点正好在这个 的正方形的中点处时,画一个半径为
的正方形的中点处时,画一个半径为 的圆,我们可以观察到只有四个地方可以放点,所以说一个点最多与另外5个点进行比较,于是将
的圆,我们可以观察到只有四个地方可以放点,所以说一个点最多与另外5个点进行比较,于是将 的效率降低到了
的效率降低到了 。
。
    那么需要查找上下为 的点我们应该怎么寻找呢?最简单的形式是将中间区域的所有点放在一个数组当中,然后延y轴从下至上进行排序,然后按顺序从下至上进行比较,这样可以遍历到所有的点。由于是从下至上进行比较,不用返回去进行比较,那么每个开始点比较的次数又可以减小。
的点我们应该怎么寻找呢?最简单的形式是将中间区域的所有点放在一个数组当中,然后延y轴从下至上进行排序,然后按顺序从下至上进行比较,这样可以遍历到所有的点。由于是从下至上进行比较,不用返回去进行比较,那么每个开始点比较的次数又可以减小。

        如图所示,因为是从下至上进行比较,那么下方的点将可以不用再进行比较,于是一个点开始时只需要跟另外三个点进行比较,比较次数时肯定小于 的。
的。
        接下来要开始考虑如何对点进行y轴排序,一开始我的想法是将点放在一个临时数组里面,并记下来点的下标,然后进行快速排序。如此一来最坏的情况合并算法效率就会是 。
。
伪代码如下:
	Merge (P,l,m,h,Min)
		for indexL=m to l
			if P[m].x-P[indexL].x>Min
				break
		for indexH=m to h
			if P[indexH].x-P[m].x>Min
				break;
		n=indexH-indexL+1
		if n>=2
			let P[indexL ... indexH] to temp[0…n]
			sort(temp)
			for i=0 to n
				for j=i+1 to n
					if temp[i].y-temp[j].y>Min
						break
					if Distance(temp[i],temp[j])<Min
						Min= Distance(temp[i],temp[j])
        前面两个for循环是为了找到中间的 范围内点的下标范围,记为indexL和indexH。当在范围内的点是大于或等于2才需要进行比较。接着将数据放入临时数组并且进行排序。后面两个for循环是用于比较各个点。这样下来最多的计算次数将会是
范围内点的下标范围,记为indexL和indexH。当在范围内的点是大于或等于2才需要进行比较。接着将数据放入临时数组并且进行排序。后面两个for循环是用于比较各个点。这样下来最多的计算次数将会是 ,那么合并算法的效率是
,那么合并算法的效率是 。
。
用主定理法计算整个算法效率。


        由于 和
和 不是在多项式意义上的大于,所以无法用主定理法。
不是在多项式意义上的大于,所以无法用主定理法。
利用递归树:

 所以 
显然这不是最快的算法。
于是要想将效率提高到就不能再合并算法中进行排序,那么我们可以运用合并排序在合并的同时进行将所有点进行延y轴排序。
        同样的取一个临时数组,点的下标存放在临时数组当中。将原来的合并函数当中的排序函数删除并且在if语句之前加上合并排序的合并函数,再遍历这个临时数组,将下标在 范围内的点下标找到再放入一个临时数组中。伪代码如下:
范围内的点下标找到再放入一个临时数组中。伪代码如下:
	Merge (P,l,m,h,Sorted_tp,tp1,tp2,Min)
		for indexL=m to l
			if P[m].x-P[indexL].x>Min
				break
		for indexH=m to h
			if P[indexH].x-P[m].x>Min
				break;
		MergeSort_Merge(Sorted_tp,tp1,tp2)
		index=0
		for i=0 to h-l+1
			if indexL<=Sorted_tp[i].No<=indexH
				temp[index++]=Sorted_tp
		n=indexH-indexL+1
		if n>=2
			for i=0 to n
				for j=i+1 to n
					if temp[i].y-temp[j].y>Min
						break
					if Distance(temp[i],temp[j])<Min
						Min= Distance(temp[i],temp[j])
        此时在最坏的情况下,运行次数大概是 ,该合并算法的效率是
,该合并算法的效率是 。
。
        同时这个算法相较于上一个算法来说,必须有两次运行到 ,而上一个算法是数据量极端的时候才有到
,而上一个算法是数据量极端的时候才有到 。如果将最大次数定位
。如果将最大次数定位 不确定的次数定位
不确定的次数定位 ,则第一个合并算法的次数是:
,则第一个合并算法的次数是: 。第二个合并算法的次数是
。第二个合并算法的次数是 。所以在数据量小且理想的情况下,第一个合并算法的效率也许会小于第二个合并算法,但是由于第一个合并算法的效率是
。所以在数据量小且理想的情况下,第一个合并算法的效率也许会小于第二个合并算法,但是由于第一个合并算法的效率是 ,当数据量十分庞大的时候还是第二个合并算法效率更高一点。
,当数据量十分庞大的时候还是第二个合并算法效率更高一点。
我们用主定理方法计算第二个分治算法的效率:


 ∵ ∴符合情况2
∴符合情况2

数据分析:
蛮力算法运行结果如下:

我们可以清楚的看到这个曲线呈现的是二次曲线,我运行的是10000到100000的数据量,当到100000数据量时,蛮力法需要53.515秒的时间,而10000数据量时需要0.543秒的时间,正好相差100倍。
        第一个效率为 的分治算法结果如下:
的分治算法结果如下:
 
        可以看到这个分治法虽然效率不是 但是依然比蛮力法快很多。这是以数据量在十万到一百万之间的数据,可以明显地看到比蛮力法快很多。相同十万个数据蛮力法需要53秒而分治法只需要0.073秒。
但是依然比蛮力法快很多。这是以数据量在十万到一百万之间的数据,可以明显地看到比蛮力法快很多。相同十万个数据蛮力法需要53秒而分治法只需要0.073秒。
        第二个效率为 的分治算法结果如下:
的分治算法结果如下:

可以看到数据基本与曲线拟合,且比蛮力法快很多。
接下来我们对比一下两个分治算法的效率。

        我们可以看到在数据量小的时候甚至效率在 的分治算法用时(0.073秒)还比效率在
的分治算法用时(0.073秒)还比效率在 的合并算法用时(0.079秒)要短,这就是我上述所说的有可能数据量小的时候第一个合并算法的用时要比第二个短。但是到了后面数据量大的时候明显第一个分治算法用时要多了。
的合并算法用时(0.079秒)要短,这就是我上述所说的有可能数据量小的时候第一个合并算法的用时要比第二个短。但是到了后面数据量大的时候明显第一个分治算法用时要多了。
我们将数据量继续扩大。

         明显的第一个分治算法要比第二个慢了,效率为 的算法在1000万数据量时用时8.646秒,效率为
的算法在1000万数据量时用时8.646秒,效率为 的算法在1000万数据量用时12.135秒。
的算法在1000万数据量用时12.135秒。
实验结论:
        本次实验体会到了分治法的有点和算法思想,他的主要思想就是将大问题缩小化,并且将尽可能的提高合并效率,合并的效率最好是线性的。本次实验我一开始没有掌握到分治法的精髓之处,没有将合并效率降低到线性,而是将合并效率达到了o(nlgn) 。于是导致了整个合并算法不是最佳的。也许在小数据量的时候并不能体会到这个差距但是当数据量十分庞大的时候就可以发现这个差距是很大的了。
。于是导致了整个合并算法不是最佳的。也许在小数据量的时候并不能体会到这个差距但是当数据量十分庞大的时候就可以发现这个差距是很大的了。










