本文参考UCAS卜东波老师算法设计与分析课程撰写
前言
本文内容承接上次分治思想(2)- 选择问题(对单个数组归约)的内容。前面一直讲述的是分治思想在数组上的运用,总的来说可以归纳为两种:
- 根据下标划分:归并
划分稳定,保证每次都能使得数据规模指数级下降,达到降维的目的,但依赖更多的空间,在大数据情况下开销较大
- 根据数值划分:快排、选择
划分不稳定,依赖于piviot的选择合适与否,但大部分情况下都能做到数据规模指数级下降,且不依赖多余空间,应用要更为广泛
在实际应用中,我们需根据需要,再自行选择划分的办法。本文主要介绍分治思想在点集当中的应用,这种应用也十分广泛,例如地图软件中各个地点的位置组合都可以看作点集,对这类问题的分治已经超过了简单的数组归约。我们用一个问题来引入:平面上最近点对寻找问题
平面上最近点对寻找
问题描述与分析
- 如题,在一个平面中有许许多多的点,构成了一个点集,我们要从中找到距离最近的两个点
最简单的策略,直接采用双重循环,对每一组点对距离进行遍历,如果小于当前最小距离,则更新(当然可以去除重复比较和自身与自身比较的情况),时间复杂度是 ,一般日常情况下我们处理的数据量级都在几千以下,因此 影响不大,但是一旦数据量上到1000000,耗时就非常可怕。下面是我写的一个例子,在复杂度 下,计算不同数据规模下两个数组差值耗时情况,直观感受一下这个复杂度:
由于1000000耗时过久,设计了超过2min就算用时很长,不再进行计算,实际上可以估计当数据量为100000时,耗时大概在5400s(1.5h)左右,1000000则更大,可以发现如果 远远无法达到商用要求。
解决思路
因此我们想到应用分治的思想,将平面点对的距离问题规模缩小,将求原平面点对距离的问题转换为求子平面内点对距离问题+子平面间点对距离问题。但在对平面划分的时候我们时刻要注意分治思想的核心,要让数据规模指数级下降。因此需要保证划分的时候两边的点对尽量均匀,对此,我们可以通过采用对横坐标求中位数或对纵坐标求中位数的办法将点对一分为二,而求中位数只需进行一次
的排序即可,如下图:
实际上我们不一定一次只分成两块,但是2块的时候我们可以通过中位数保证数据呈指数级下降,如果是4块或以上,则可能出现数据量划分十分不均的情况,如下面的例子,我们无法通过横竖各一刀将其均匀划分,你可以尝试一下,想想原因。
划分完成了,显然有以下递推式:
其中,
为求子平面间点对距离耗时
根据Master定理,为了让该复杂度低于
,
的复杂度必须至少小于
,因此我们主要探讨如何减少子平面间点对距离求解。
关于Master定理不了解的可以看我这篇文章:算法设计与分析:分治思想 - 入门
子平面间点对距离求解
如果采用最直白的遍历两个子平面的点对,仍然是 ,不符合要求。因此我们开始想办法利用分治的结果来减少冗余计算。
依据横纵坐标与子平面归约结果减少计算
在划分成2个子平面后,假设我们已经递归得到两个子平面内的最小距离min。因此两个子平面间的点对最小距离若大于min,则此距离必然不需要计算,因此我们只需考虑(设点i在左子平面,点j在右子平面):
为了找到满足这两特性的点对,我们需要做如下两个操作:
- 以划分线为界,左右各
的距离,如下红色区域所示,非红色区域内的点无需考虑。
这一点应该很好理解,只有红色区域之内的两个点才有可能更新最小距离 - 对红色区域的位置按照横纵
划分格子,如下
这种划分方式有两种好处:一方面保证每个格子中只有一个点(格子中点对距离最大为 ),另一方面保证只有相邻3行的格子可能距离小于min(如果属于中间间隔两行,距离必然在min之上)。
完成上面两步操作之后,我们再看对于红色区域中的某个点,我们需要考虑的只有6个格子。下面是一个实例:
这里解释一下,我们只考虑比较子平面间的点,因此(1,2,5,6,9,10)和(3,4,7,8,11,12)两个集合内之间元素不需要相互比较,对于6号点,可能与右边6个点进行比较,对于9号点则更少(只需和3,7,11比较)
因此,我们只需将这些点按照纵坐标排序后,计算其与其后11个点的距离(这11个点必然包括必须的6个点),再与min比较即可。
-
伪代码
sort(p,0,n) // 对0到n区间的点对依据横坐标进行排序,用于找到中位数 closest_pair(l,r): if r-l==1: return d(p[l],p[r]) 依据 p[(l+r)/2]横坐标x将p[l],p[l+1],...,p[r]划分成两部分 min_l = cloest_pair(l,(l+r)/2) // T(n/2) min_r = cloest_pair((l+r)/2,r) // T(n/2) min_d = min(min_l,min_r) // 获得子平面内最小距离 对距离分界线min_d距离的点按照纵坐标y排序,得到点集K // O(nlogn) for p_i in K: // O(n) for p_j = p_i+1 to p_j+11: if d(p_i,p_j) < min: min = d(p_i,p_j) return min
-
时间复杂度分析
主要耗时在递归和按照y排序的部分,得到时间复杂度递推公式:
依据Master定理,最终时间复杂度为
这个复杂度比 已经降低了许多,那么是否有办法更进一步,将复杂度再次降低?对于递归成2个子平面的时间不好改进,考虑从费时的按照y坐标进行排序入手,这里时间之所以是 ,是因为需要从头对点集按照y进行排序,实际上,这个步骤可以像找中位数的点一样,放到外面去做。在对点集按照x排序后,再按照纵坐标y排序一次,这个时候我们同时需要维护两个列表,这两个列表分别保存按照x和按照y排序的点的顺序。如下图例所示:
其中,同颜色的点表示有序。按y排序这个操作是跟在按x排序后的(对两个子平面分别操作),两者都是 ,所以对整体复杂度没影响。按y排序前,分界线两边的点要按y排序需要从头排序 ,按y排序后,分界线两边的点要按y排序则只需归并排序中的并操作,复杂度为
因此,改进后的复杂度为:
这个策略要耗费多一倍空间用于存储按y排序的列表,这也是典型的空间换时间策略。
总结
本文由平面最近点对问题入手,详细阐述了如何将分治思想应用于点集的归约上,其中需要重点掌握对子平面间点对距离的探讨,如何从一开始的 一步步去冗余计算,最终降低到 ,这个分析过程十分值得反复推敲。其中有两个重要步骤:
- 按照x坐标划分出两个条带区域(红色区域),这不仅减少了许多不必要的点对计算,也为后面有限个点对间(11)计算打下了基础
- 按照y坐标将条带按格子划分,这个操作有两个重要的好处,其一是保证每个格中只有一个点,其二是保证点对的比较数量是个常数,与n无关。最后的复杂度优化也是因为观察到了排序,这里和前面的内容产生了联系。
在实际的应用过程中,遇到平面点对问题的求解即可参考上面的思考模式,将一个复杂问题慢慢化简,最终获得解答。
如果你觉得文章对你有用,不妨顺手点个赞哦~