背景
在二维平面上有很多点,目标是给每个点设置一个序号,让人感觉这些序号是从上到下从左到右排列的。
我们用C++实现,假设点的结构如下::
struct Point {
int x;
int y;
};
直觉
乍看这个问题好像不难解决,直接给std::sort写一个自定义compare函数,按照点的x、y排序就行了,具体如下:
std::sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
if (a.y == b.y) { // 垂直方向在同一行,则从左到右排
return a.x < b.x;
}
return a.y < b.y; // 其他情况从上到下排
});
这样也确实解决了问题,能够对点进行从上到下从左到右排序。
但在实际应用中有些问题,如下图所示,在视觉上,最左边的两个点的序号应该比较近,也就是下面绿色的排序方案。但按照上述代码排序,会给出上面红色的排序方案。
调整
那么如何调整,能够实现视觉上的排序呢?自然而然地想到在比较y时增加一定的缓冲空间,具体来说就是两个Point在垂直方向比较接近时( ∣ y 1 − y 2 ∣ < 50 |y_1 - y_2|<50 ∣y1−y2∣<50)时,认为他们处于同一列,按从左到右排列(也就是比较 x x x)。具体实现如下:
// std::vector<Point> points;
std::sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
if (std::abs(a.y - b.y) < 50) { // 纵向相差50以内,视为同一行
return a.x < b.x;
}
return a.y < b.y;
});
这种解决方案确实给出了上图绿色区域中的排序,在前期也没有发现有什么问题,但随着点的数量增加,出现了排序结果不一致的问题,也就是对同一个数组多次排序,结果却不相同。
大家觉得可能是什么原因造成的?
这里就要提到std::sort对compare函数的要求:严格弱序 strict weak ordering ,其具体要求有如下3点:
- For all a, comp(a, a) == false.
- If comp(a, b) == true then comp(b, a) == false.
- If comp(a, b) == true and comp(b, c) == true then comp(a, c) == true.
上面给出的compare
函数违反了第3个要求,也就是比较的传递性,可能会出现a < b
,b < c
但a > c
的情况,比如:
a
=
(
0
,
51
)
b
=
(
1
,
49
)
c
=
(
2
,
0
)
a = (0, 51)\\ b = (1, 49)\\ c = (2, 0)\\
a=(0,51)b=(1,49)c=(2,0)
解决
那么想要实现,通过compare
函数能够解决吗?单纯实现一个compare
函数无法实现我们想要的这种排序,需要组合多个排序来实现。
具体来说就是:
- 按照的数值对点分组,从上到下每50分为一组,如下图所示
- 不同组的点按照从上到下排序
- 同一组的点按照从左到右排序
具体实现:
std::vector<Point> sortPoints(std::vector<Point> points, int epsilon) {
if (points.empty()) return points;
std::sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
return a.y < b.y;
});
std::vector<std::vector<Point>> groups;
int start_y = points[0].y;
std::vector<Point> currentGroup;
for (const auto& p : points) {
if (p.y >= (start_y - epsilon)) {
currentGroup.push_back(p);
} else {
groups.push_back(currentGroup);
currentGroup.clear();
start_y = p.y;
currentGroup.push_back(p);
}
}
groups.push_back(currentGroup);
// 每个组内按x坐标升序排序
for (auto& group : groups) {
std::sort(group.begin(), group.end(), [](const Point& a, const Point& b) {
return a.x < b.x;
});
}
// 合并所有组
std::vector<Point> result;
for (const auto& group : groups) {
result.insert(result.end(), group.begin(), group.end());
}
return result;
}
思考
这种方案在根本上解决我们的问题了吗?
答案是否定的,这种按y分组的情况,可能将本来在垂直方向(y)接近的点分在不同组,如下图中的红色点:
对这个问题,你有更好的解决方案吗?
另外,如果想在比较x时也增加一定的模糊空间,又该如何做呢?