平面最近点对算法
1、问题
平面最近点对问题,给定一个平面内所有点的坐标,找出这些点中最近的两个点的距离。
2、解析
设平面上的点都在点集S中,为了将S线性分割为大小大致相等的2个子集S1和S2,我们选取一垂直线(x=m)来作为分割直线,其中m为S中各点x坐标的中位数。由此将S分割为S1={p∈S|px≤m}和S2={p∈S|px>m}。从而使S1和S2分别位于直线l的左侧和右侧,且S=S1∪S2 。由于m是S中各点x坐标值的中位数,因此S1和S2中的点数大致相等。 递归地在S1和S2上解最接近点对问题,我们分别得到S1和S2中的最小距离δ1和δ2。现设δ=min (δ1,δ2)。若S的最接近点对(p,q)之间的距离d(p,q)<δ则p和q必分属于S1和S2。不妨设p∈S1,q∈S2。那么p和q距直线l的距离均小于δ。因此,我们若用P1和P2分别表示直线l的左边和右边的宽为δ的2个垂直长条,则p∈S1,q∈S2.此时,P1中所有点与P2中所有点构成的点对均为最接近点对的候选者。可证明这种投影点最多只有6个。因此,若将P1和P2中所有S的点按其y坐标排好序,则对P1中所有点p,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者,对P1中每一点最多只要检查P2中排好序的相继6个点。
3、设计
点归并
1 void merge(int l, int r) {//归并排序l到r区间内的点 2 mid = l + r >> 1 3 pos1 = l 4 pos2 = mid + 1 5 for i <- l to r 6 if p[pos1].y < p[pos2].y 7 temp[i] = p[pos1++] 8 else 9 temp[i] = p[pos2++] 10 if pos1 == mid + 1 11 break 12 if pos2 == r + 1 13 break 14 15 for j <- i to r and pos1 <= mid 16 temp[j] = p[pos1++] 17 for j <- i to r and pos2 <= r 18 temp[j] = p[pos2++] 19 for k <- l to r 20 p[i] = temp[i] 21 }
分治思想分割点
1 int GetMinDistance(int l, int r) { 2 if l >= r 3 return INT_MAX;//如果只有一个点返回无穷大 4 if l + 1 == r //如果只有两个点的话,l点在下,r点在上 5 if (p[l].y > p[r].y) 6 swap(p[l], p[r]) 7 return dis(l, r) 8 } 9 mid = l + r >> 1 10 PointMid_x = p[mid].x 11 cnt = 0 12 d = min(GetMinDistance(l, mid), GetMinDistance(mid + 1, r))//左右两个区间取最近距离 13 merge(l, r)//归并区间 14 for i <- l to r//找出里中心线距离为d之间的点 15 if (p[i].x - PointMid_x) * (p[i].x - PointMid_x) < d 16 interpoint[++cnt] = i 17 for i <- 1 to cnt//遍历找出的点,修改最小值d 18 for j <- i + 1 to cnt 19 dist = (p[interpoint[j]].x - p[interpoint[i]].x) * (p[interpoint[j]].y - p[interpoint[i]].y) 20 if dist >= d 21 break 22 d = min(d, dis(interpoint[i], interpoint[j])); 23 } 24 } 25 return d//返回最小值d 26 }
4、分析
平面点对算法需要多次分治和进行子问题合并操作
合并的复杂度为O(n),分治的子问题复杂度为 O(n/2)
总时间复杂度为O(n)+2*O(n/2)=O(nlogn)
5、源码
https://github.com/ChenyuWu0705/Algorithm-Analyze-and-Design/blob/main/PointMinDistance.cpp