平面最近点对
概述¶
给定
下面我们介绍一种时间复杂度为
算法¶
与常规的分治算法一样,我们将这个有
我们先将所有点按照
并递归下去,求出两点集各自内部的最近点对,设距离为
现在该合并了!我们试图找到这样的一组点对,其中一个属于
结合图像,直线
再根据
对于
在点集
如果我们将
由此我们得到了合并的步骤:
- 构建集合
B - 将
B y_i O(n\log n) O(n) - 对于每个
p_i \in B p_j \in C(p_i) (p_i,p_j)
注意到我们上文提到了两次排序,因为点坐标全程不变,第一次排序可以只在分治开始前进行一次。我们令每次递归返回当前点集按
似乎这个算法仍然不优,
复杂度证明¶
我们已经了解到,
我们再将这个矩形拆分为两个
我们将一个
由此,每个正方形中最多有
实现¶
我们使用一个结构体来存储点,并定义用于排序的函数对象:
结构体定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | struct pt {
int x, y, id;
};
struct cmp_x {
bool operator()(const pt& a, const pt& b) const {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
};
struct cmp_y {
bool operator()(const pt& a, const pt& b) const { return a.y < b.y; }
};
int n;
vector<pt> a;
|
为了方便实现递归,我们引入 upd_ans()
辅助函数来计算两点间距离并尝试更新答案:
答案更新函数
1 2 3 4 5 6 7 8 | double mindist;
int ansa, ansb;
inline void upd_ans(const pt& a, const pt& b) {
double dist =
sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + .0);
if (dist < mindist) mindist = dist, ansa = a.id, ansb = b.id;
}
|
下面是递归本身:假设在调用前 a[]
已按
我们使用 std::merge()
来执行归并排序,并创建辅助缓冲区 t[]
,
主体函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | void rec(int l, int r) {
if (r - l <= 3) {
for (int i = l; i <= r; ++i)
for (int j = i + 1; j <= r; ++j) upd_ans(a[i], a[j]);
sort(a + l, a + r + 1, &cmp_y);
return;
}
int m = (l + r) >> 1;
int midx = a[m].x;
rec(l, m), rec(m + 1, r);
static pt t[MAXN];
merge(a + l, a + m + 1, a + m + 1, a + r + 1, t, &cmp_y);
copy(t, t + r - l + 1, a + l);
int tsz = 0;
for (int i = l; i <= r; ++i)
if (abs(a[i].x - midx) < mindist) {
for (int j = tsz - 1; j >= 0 && a[i].y - t[j].y < mindist; --j)
upd_ans(a[i], t[j]);
t[tsz++] = a[i];
}
}
|
在主函数中,这样开始递归即可:
调用接口
1 2 3 | sort(a, a + n, &cmp_x);
mindist = 1E20;
rec(0, n - 1);
|
推广:平面最小周长三角形¶
上述算法有趣地推广到这个问题:在给定的一组点中,选择三个点,使得它们两两的距离之和最小。
算法大体保持不变,每次尝试找到一个比当前答案周长
非分治算法¶
其实,除了上面提到的分治算法,还有另一种时间复杂度同样是
我们可以考虑一种常见的统计序列的思想:对于每一个元素,将它和它的左边所有元素的贡献加入到答案中。平面最近点对问题同样可以使用这种思想。
具体地,我们把所有点按照
- 将所有满足
x_i - x_j >= d - 对于集合内满足
\lvert y_i - y_j \rvert < d p_i - 将
p_i
由于每个点最多会被插入和删除一次,所以插入和删除点的时间复杂度为
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include <algorithm>
#include <cmath>
#include <cstdio>
#include <set>
const int N = 200005;
int n;
double ans = 1e20;
struct point {
double x, y;
point(double x = 0, double y = 0) : x(x), y(y) {}
};
struct cmp_x {
bool operator()(const point &a, const point &b) const {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
};
struct cmp_y {
bool operator()(const point &a, const point &b) const { return a.y < b.y; }
};
inline void upd_ans(const point &a, const point &b) {
double dist = sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
if (ans > dist) ans = dist;
}
point a[N];
std::multiset<point, cmp_y> s;
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%lf%lf", &a[i].x, &a[i].y);
std::sort(a, a + n, cmp_x());
for (int i = 0, l = 0; i < n; i++) {
while (l < i && a[i].x - a[l].x >= ans) s.erase(s.find(a[l++]));
for (auto it = s.lower_bound(point(a[i].x, a[i].y - ans));
it != s.end() && it->y - a[i].y < ans; it++)
upd_ans(*it, a[i]);
s.insert(a[i]);
}
printf("%.4lf", ans);
return 0;
}
|
习题¶
- UVA 10245 "The Closest Pair Problem"[难度:低]
- SPOJ #8725 CLOPPAIR "Closest Point Pair"[难度:低]
- CODEFORCES Team Olympiad Saratov - 2011 "Minimum amount"[难度:中]
- SPOJ #7029 CLOSEST "Closest Triple"[难度:中]
- Google Code Jam 2009 Final "Min Perimeter"[难度:中]
参考资料与拓展阅读¶
本页面中的分治算法部分主要译自博文 Нахождение пары ближайших точек 与其英文翻译版 Finding the nearest pair of points。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:OI-wiki
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用