问题概述为在N个顶点(带有x,y坐标)中寻找两定点间的最短距离;
比较详细的理论分析可以参考下面的博客文章,个人认为大部分解释的比较清楚:https://blog.csdn.net/chenxianqin2/article/details/79068975?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
另外附上两张参考图:
x轴中线划分参考图:
排除x坐标和中线距离超过delta的顶点的参考图:
先稍作总结如下(具体一些的叙述与实现一起说明):
首先对x坐标进行排序以便二分(二分时需要注意中间元素两边都需要包括),y排序版本备用;
递归查找两个顶点均在左侧以及均在右侧的情况,返回该范围的最小距离,将两者的较小者设为delta;
对于一左一右的情况,首先从y排序版本中去除x坐标距离中线距离超过delta的顶点,然后双重遍历,排除y坐标差大于delta的情况(出现大于的情况时直接外层循环进入下一个顶点),并在两顶点距离小于delta时进行更新;
下面是自己的实现:
首先是点类对象,其中包含xy坐标,默认构造函数使用随机数生成器和分配对象创建0-10内随机浮点数作为xy坐标;重载<<用于输出点;以及一些与点相关的操作,Dist求两点距离,LesserX,LesserY使用x,y坐标进行排序,用于main函数中对所有点按x,y坐标进行排序,作为sort函数的参数;
class Point {
public:
double x;
double y;
Point(double n1, double n2, int l) : x(n1), y(n2){}
Point() {
std::random_device rd;
std::default_random_engine rng(rd());
std::uniform_real_distribution<> dist(0, 10);
x = dist(rng);
y = dist(rng);
}
friend std::ostream& operator<<(std::ostream& os, const Point& p)
{
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
double Dist(const Point &p1, const Point &p2) {
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
}
bool LesserX(const Point &p1, const Point &p2)
{return p1.x < p2.x;}
bool LesserY(const Point &p1, const Point &p2)
{return p1.y < p2.y;}
然后是主程序与实现函数:
主程序说明如下:
首先创建一定数量的随机点加入points容器,分别创建副本并用x,y进行从小到大的排序;
调用获取最小距离的函数(其中前两个参数是当前所有顶点按x,y排序版本的容器,lc,rc为x轴的左右游标,l1,l2为两个指针,记录两个最大距离顶点的编号;
函数说明如下:
首先写明base case,因为计算距离最少需要两个顶点,所以基础情况为两个顶点的情况,直接将顶点编号设为左右游标,并计算两点距离返回即可;
如果不止两个顶点,则进行二分,需要注意要将mid顶点包含在左右两部分中,以保证在有3个顶点的情况时,划分后左右均会有两个顶点来计算距离,即顶点1,2和顶点2,3;
使用x坐标划分后拆分出两侧各自的按y坐标排序的版本(将用于strip area中最小距离的寻找),即两侧各自包含的顶点并按照y坐标从小到大排序,拆分的方式即遍历当前的src_y,并按照各个点x坐标的值(与按x排序中的mid比较)划分至两侧,同样位于x坐标中间的点两侧均需要包含;
递归调用先计算左右两侧单独的最小距离以及对应的顶点编号,并先获取较小者作为暂时的最小值delta和最小顶点编号;
然后开始计算一左一右的情况,首先遍历将src_y中x坐标距离mid超过delta的顶点移除,因为光距离中线就到达delta,与右侧顶点相连距离必然超过delta,即当前的最短距离;不必考虑中线上的顶点,因为中线上的顶点既被包含在左侧又被包含在右侧,即顶点与中间线上顶点的距离已经在单侧计算中被考虑过了;
清除x坐标超标的顶点后如果仍有两个或以上的顶点(如果没有则说明不存在一左一右两个顶点有可能存在更小距离的情况),则开始从前向后双重遍历(由于按y排序的容器为小到大排序,即相当于图中从下向上遍历),如果后面的顶点和前面顶点y轴距离超过delta,则也可直接排除并break进入外层的下一轮循环(因为后续顶点的y只会更大,更加超过delta),如果y轴距离未超标则测试距离,如果大于delta也排除,如果小于则更新delta,并通过遍历src_x数组(排序后一直没有调整过)寻找的方式来查找顶点编号(因为src_y数组经过不同标准的排序,拆分,和删除超标顶点后早已失去了顶点位置信息)并更新顶点编号;
最后返回最小距离完成函数;
注意这里返回的顶点编号为使用x坐标从小到大排序后的index值,意义不是很大,仅作为尝试,但是也可以通过与原顶点数组的匹配寻找原数组中的编号,修改也并不复杂;
整体复杂度包括排序,遍历,双重遍历等,排序默认为O(NlogN),双重遍历虽然使用循环嵌套,但可以证明每个顶点在内层循环遍历的顶点不会超过7个,因此复杂度为O(N),所以整体复杂度为O(NlogN);
int main() {
vector<Point> points;
for (int i = 0; i < 10; ++i)
{
Point tmp_point;
points.push_back(tmp_point);
}
vector<Point> points_x(points), points_y(points);
sort(points_x.begin(), points_x.end(), LesserX);
sort(points_y.begin(), points_y.end(), LesserY);
int l1, l2;
double min = MinDistance(points_x, points_y, 0, points_x.size()-1,
&l1, &l2);
cout.setf(ios_base::fixed);
cout.precision(2);
cout << "points are:\n";
for (auto& entry : points_x)
cout << entry << " ";
cout << endl;
cout << "minimum distance is " << min << endl;
cout << "points label are: " << l1 << " and " << l2 << endl;
return 0;
}
double MinDistance(vector<Point> &src_x, vector<Point> &src_y, int lc, int rc,
int *l1, int *l2)
{
if (lc == rc - 1)
{
*l1 = lc;
*l2 = rc;
return Dist(src_x[lc], src_x[rc]);
}
int mid = (lc + rc)/2;
vector<Point> src_yl, src_yr;
for (auto& entry : src_y)
{
if (entry.x <= src_x[mid].x)
src_yl.push_back(entry);
if (entry.x >= src_x[mid].x)
src_yr.push_back(entry);
}
int ll1, ll2, rl1, rl2, cl1, cl2;
double minL = MinDistance(src_x, src_yl, lc, mid, &ll1, &ll2);
double minR = MinDistance(src_x, src_yr, mid, rc, &rl1, &rl2);
double delta = min(minL, minR);
if (delta == minL)
{
*l1 = ll1;
*l2 = ll2;
} else
{
*l1 = rl1;
*l2 = rl2;
}
vector<Point>::iterator iter;
for (iter = src_y.begin(); iter != src_y.end(); ++iter)
{
if (abs((*iter).x - src_x[mid].x) > delta)
{
src_y.erase(iter);
iter--;
}
}
if (src_y.size() >= 2)
for (int i = 0; i < src_y.size(); ++i)
for (int j = i+1; j < src_y.size(); ++j)
{
if (src_y[j].y - src_y[i].y > delta)
break;
if (Dist(src_y[i], src_y[j]) < delta)
{
delta = Dist(src_y[i], src_y[j]);
for (int k = 0; k < src_x.size(); ++k)
{
if (src_x[k].x == src_y[i].x && src_x[k].y == src_y[i].y)
cl1 = k;
if (src_x[k].x == src_y[j].x && src_x[k].y == src_y[j].y)
cl2 = k;
*l1 = cl1;
*l2 = cl2;
}
}
}
return delta;
}