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

源代码:

深大算法设计与分析实验二——分治法求最近点对问题代码-C/C++文档类资源-CSDN下载

目录

实验问题

一、实验目的:

二、内容:

三、算法思想提示

产生不重复的随机点算法:

蛮力算法:

分治算法

数据分析:

实验结论:


实验问题

一、实验目的:

  1. 掌握分治法思想。
  2. 学会最近点对问题求解方法。

二、内容:

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)

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个点进行比较,于是将的效率降低到了

    那么需要查找上下为的点我们应该怎么寻找呢?最简单的形式是将中间区域的所有点放在一个数组当中,然后延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循环是用于比较各个点。这样下来最多的计算次数将会是,那么合并算法的效率是

        用主定理法计算整个算法效率。

        由于不是在多项式意义上的大于,所以无法用主定理法。

        利用递归树:

 所以

        显然这不是最快的算法。

        于是要想将效率提高到就不能再合并算法中进行排序,那么我们可以运用合并排序在合并的同时进行将所有点进行延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])

        此时在最坏的情况下,运行次数大概是,该合并算法的效率是

        同时这个算法相较于上一个算法来说,必须有两次运行到,而上一个算法是数据量极端的时候才有到。如果将最大次数定位不确定的次数定位,则第一个合并算法的次数是:。第二个合并算法的次数是。所以在数据量小且理想的情况下,第一个合并算法的效率也许会小于第二个合并算法,但是由于第一个合并算法的效率是,当数据量十分庞大的时候还是第二个合并算法效率更高一点。

        我们用主定理方法计算第二个分治算法的效率:

T(n)=2T(n/2)+n

 ∵∴符合情况2

数据分析:

        蛮力算法运行结果如下:

      我们可以清楚的看到这个曲线呈现的是二次曲线,我运行的是10000到100000的数据量,当到100000数据量时,蛮力法需要53.515秒的时间,而10000数据量时需要0.543秒的时间,正好相差100倍。

        第一个效率为的分治算法结果如下:

 

        可以看到这个分治法虽然效率不是但是依然比蛮力法快很多。这是以数据量在十万到一百万之间的数据,可以明显地看到比蛮力法快很多。相同十万个数据蛮力法需要53秒而分治法只需要0.073秒。

        第二个效率为的分治算法结果如下:

        可以看到数据基本与曲线拟合,且比蛮力法快很多。

        接下来我们对比一下两个分治算法的效率。

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

        我们将数据量继续扩大。

         明显的第一个分治算法要比第二个慢了,效率为的算法在1000万数据量时用时8.646秒,效率为的算法在1000万数据量用时12.135秒。

实验结论:

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

  • 13
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个使用分治法解最大子数组问题实验过程和结果: 1. 实验背景 最大子数组问题是指在一个数组中找到一个连续的子数组,使得子数组中元素的和最大。这是一个经典的算法问题,可以使用多种算法解,其中一种较为常见的方法是使用分治法。 2. 实验原理 分治法是一种将问题分解为子问题来解决的算法,它将一个大问题分解为若干个规模较小的子问题,然后分别解决这些子问题,最后将它们的解合并起来得到原问题的解。 在使用分治法解最大子数组问题时,可以将原数组分成两部分,分别出左半部分、右半部分和跨越中的最大子数组。其中左半部分和右半部分的最大子数组可以通过递归解,而跨越中的最大子数组可以通过扫描数组来解。 3. 实验步骤 为了验证分治法解最大子数组问题的有效性,我们可以进行以下实验步骤: (1)生成一个含有n个元素的随机数组,其中n为实验参数,元素取值范围为[-100, 100]。 (2)使用分治法解最大子数组问题,记录算法运行时间和解结果。 (3)使用暴力算法解最大子数组问题,记录算法运行时间和解结果。 (4)比较两种算法解结果和运行时间,验证分治法的有效性。 4. 实验结果 我们使用Python语言实现了上述实验步骤,其中分治法的实现代码如下: ```python def find_maximum_subarray(a, low, high): if low == high: return low, high, a[low] else: mid = (low + high) // 2 left_low, left_high, left_sum = find_maximum_subarray(a, low, mid) right_low, right_high, right_sum = find_maximum_subarray(a, mid+1, high) cross_low, cross_high, cross_sum = find_maximum_crossing_subarray(a, low, mid, high) if left_sum >= right_sum and left_sum >= cross_sum: return left_low, left_high, left_sum elif right_sum >= left_sum and right_sum >= cross_sum: return right_low, right_high, right_sum else: return cross_low, cross_high, cross_sum def find_maximum_crossing_subarray(a, low, mid, high): left_sum = -float("inf") sum = 0 for i in range(mid, low-1, -1): sum += a[i] if sum > left_sum: left_sum = sum max_left = i right_sum = -float("inf") sum = 0 for j in range(mid+1, high+1): sum += a[j] if sum > right_sum: right_sum = sum max_right = j return max_left, max_right, left_sum + right_sum ``` 其中find_maximum_subarray函数使用递归的方式来解最大子数组,find_maximum_crossing_subarray函数用于解跨越中的最大子数组。 我们使用一个含有1000个元素的随机数组进行实验,运行结果如下: ``` 分治法解结果:(54, 567, 1141) 分治法运行时间:0.0004s 暴力算法解结果:(54, 567, 1141) 暴力算法运行时间:0.0026s ``` 从上述实验结果可以看出,分治法解最大子数组问题的运行时间远远低于暴力算法,且解结果相同,说明分治法是一种有效的算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anakin Skywalker RM 00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值