用分治法解最近对问题
1.问题
设p1=(x1,y1), p2(x2,y2), …,pn=(xn,yn)是平面上n个点构成的集合S,找出集合S中距离最近的点对。
2.解析
分治法求解:
① 划分
将集合S分成两个大小基本相等的子集S1和S2
② 求解子问题
递归地求解两个子问题
③ 合并问题的解
可能出现三种情况
Ⅰ.组成S的最近点对的2个点都在S1中
Ⅱ.组成S的最近点对的2个点都在S2中
Ⅲ.组成S的最近点对的2个点分别在S1和S2中
比较三种情况下最近点对,取三者之中较小者为原问题的解
3.设计
核心代码实现:
float ClosestPair(Point points[], int length, Point &a, Point &b){ // 求出最近点对记录,并将两点记录再a、b中
double distance; //记录集合points中最近两点距离
double d1, d2; //记录分割后两个子集中各自最小点对距离
int i = 0, j = 0, k = 0, x = 0; //用于控制for循环的循环变量
Point a1, b1, a2, b2; //保存分割后两个子集中最小点对
if (length < 2)
return INFINITE_DISTANCE; //若子集长度小于2,定义为最大距离,表示不可达
else if (length == 2){ //若子集长度等于2,直接返回该两点的距离
a = points[0];
b = points[1];
distance = Distance(points[0], points[1]);
}
else{ //子集长度大于3,进行分治求解
Point *pts1 = new Point[length]; //开辟两个子集
Point *pts2 = new Point[length];
sort(points, points + length, compareX); //调用sort函数对points进行排序,compareX为自定义的排序规则
double mid = points[(length - 1) / 2].x; //排完序后的中间下标值,即中位数
for (i = 0; i < length / 2; i++)
pts1[i] = points[i];
for (int j = 0, i = length / 2; i < length; i++)
pts2[j++] = points[i];
d1 = ClosestPair(pts1, length / 2, a1, b1); //分治求解左半部分子集的最近点
d2 = ClosestPair(pts2, length - length / 2, a2, b2); //分治求解右半部分子集的最近点
if (d1 < d2) { distance = d1; a = a1; b = b1; } //记录最近点,最近距离
else { distance = d2; a = a2; b = b2; }
Point *pts3 = new Point[length];
for (i = 0, k = 0; i < length; i++)
if (abs(points[i].x - mid) <= distance)
pts3[k++] = points[i];
sort(pts3, pts3 + k, compareY); // 以y排序矩形阵内的点集合
for (i = 0; i < k; i++){
if (pts3[j].x - mid >= 0) // 只判断左侧部分的点
continue;
x = 0;
for (j = i + 1; j <= i + 6 + x && j < k; j++){ //只需与有序的领接的的6个点进行比较
if (pts3[j].x - mid < 0){ //假如i点是位于mid左边则只需判断在mid右边的j点即可
x++;
continue;
}
if (Distance(pts3[i], pts3[j]) < distance){ //如果跨分割线的两点距离小于已知最小距离,则记录该距离和两点
distance = Distance(pts3[i], pts3[j]);
a = pts3[i];
b = pts3[j];
}
}
}
}
return distance;
}
4.分析
时间复杂度:O(3nlogn)