编程之美-2.11-寻找最近点对
1. 简述
给定平面上N个点的坐标,找出距离最近的两个点。
2. 思路
暴力的方法是C(N,2),N^2的复杂度。
二分的方法,比如首先将所有的点根据横坐标排序,递归二分。终止条件:直到只有一个点或者两个点的时候返回,一个点时返回空,两个点时返回这两个点构组对。递归过程:先计算左边N/2个点之间的最近点对,计算右边N/2个点之间的最近点对,然后计算左边点与右边点构成的最近点对。这样复杂度公式:T(n) = 2T(n/2) + n^2/4,根据主定理,得到T(n)=n^2/4,还是N^2级别的。
二分的方法进一步化简,复杂度都浪费在(左边的某个点,右边的某个点)这的组合上了。假设MinDistLeft=左边最近点对的距离,MinDistRight=右边最近点对的距离,这样已知的最近点对距离可以表示为C=Min{MinDistLeft, MinDistRight},左边的最后一个点坐标为(x1, y1),右边第一个点的坐标为(x2, y2),那么左边合法的点的横坐标必须在(x2-C, x2]的范围内,右边合法的点的横坐标必须是在[x1, x1+C)的范围内,因为如果来个点横坐标就相差了C以上的话,那么距离比如大于C,因此这样做完之后,需要判断的点对的数量可能就会减少不少。
二分的方法再进一步化简,前一步化简化将左右两边的点控制在分别控制在(x2-C, x2)和[x1,x1+C)的范围内,实际上x1<=x2,因此就相当于一个2*C的两条竖线之间的点。前一步是考虑横坐标,这一步该考虑纵坐标了,对于左边和右边的点合在一个列表并按照纵坐标排序,并且标记每个点是左边的还是右边的。然后遍历这个列表,对于当前点只计算这个点后面的不同来自不同方向且纵坐标差小于C的点,如果距离超过了C,那么当前点计算停止。即每个点考虑的是其对角方向的一个C*C方框内的点,根据编程之美上的说法,这样的点只有四个,因为如果超过4个的话(此时应该是方框的4个脚),那么在前面递归分别左右两边的时候,就能得到比C更小的值了,因此遍历列表的计算次数应该是每个最多计算4次,当然算上自己这边的方框的话,那么最多比较7次(那3次就是自己这边的点)。
好了算算复杂度,这里暂时假设范围控制后,点的个数没减少,那么排序就是n*logn,遍历列表就是n*7。T(n) = 2T(n/2) + n*logn + 7*n。根据主定理,复杂度为n*logn+7*n,是n*logn级别的。
3. 代码
不写了,最近时间太紧了,后面会尽量写一点好写的,这个主要是测试数据不太好搞。
4. 扩展问题
第一个是给一个数组,求相邻数值之间的差值的最大值。所谓相邻数值,就是对于X,Y两个数值,不存在数组中其他的数在[X,Y]范围内。一般就是排序,然后前后两个数值的差来更新最大的差值。书上用了抽屉原理+桶子法,先计算出数组中最大值和最小值,可知最大差值比然大于等于delta=(Max-Min)/(N-1),这个很好证明,反证法,如果最大差值都小于delta,那么N个数值,中间N-1个差值,最小值和最大值的差就小于(delta*(N-1)),即最小值和最大值的差小于(Max-Min),矛盾了。然后把[Min,Max]分为N份,然后把不同的数值放到对应桶子里,然后只考虑前后非空的两个桶子的前一个桶子的最后一个数值与后一个桶子的第一个数值的差值即可。
对于一个数值,可以把x映射到(x-Min)/delta号桶子中。由于可能有多个数值在一个桶子中,因此用一个邻接链表可能更好,每个链表内部记录最小值和最大值。其实当最大值和最小值差值较小的时候,用计数排序的方法也可以的,就是开Max-Min+1长度的空间,每个数值可以直接放进去,计算不为空的两个数之间的差。不过计数排序的性能依赖于最大值与最小值的差的大小,以及数值要整数。
第二个题目是给定平面上的N个点,求距离最远的两个点。这个题目没多想,感觉用二分也是可以的,细想一下两次化简都不行,因为即使两个点横坐标或者纵坐标差小于C,两个点的距离仍旧肯能超过C。寻找最远点对(一)这篇博文上面的方法好像是先按照极角排序,然后求凸包,紧着用卡壳旋转方法,复杂度能到n*logn,卡壳旋转好像不是一下能看懂的,貌似是计算几何方面的,暂时这样了,以后研究吧。
5. 参考
编程之美,2.11节,寻找最近点对