closest two points problem,最近点对问题,使用分治思想(divide and conquer),复杂度O(NlogN)

问题概述为在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轴中线划分
排除x坐标和中线距离超过delta的顶点的参考图:
排除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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近问题是指在平面上给定N个,求其中距离最近的两个之间的距离。分治法是一种常用的解决最近问题算法分治法的基本思想是将问题分解成多个子问题,然后递归地解决这些子问题,最后将子问题的解合并起来得到原问题的解。对于最近问题,我们可以采用以下的分治算法: 1. 将所有按照横坐标从小到大排序,然后将它们划分为两部分,分别处理左右两个部分。 2. 对于左右两个部分,分别递归求解最近对距离。 3. 将左右两个部分中距离最近对的距离记为d。 4. 扫描所有横坐标在中间区域内的,按照纵坐标从小到大排序。对于每个,只需考虑它与纵坐标差不超过d的之间的距离,因为这些是可能成为最近对的候选者。 5. 对于每个,只需要考虑它与后面7个之间的距离即可,因为如果有距离更近的,它们之间的距离一定小于d。 6. 扫描完中间区域内的所有后,最近对的距离就是左右两个部分中的最小值和中间区域内的最小值中的较小值。 下面是该算法的伪代码: ``` function closest_pair(points): // 按照横坐标排序 sort(points, key=lambda p: p.x) // 递归终止条件 if len(points) <= 3: return brute_force(points) // 求解左右两个部分的最近对距离 mid = len(points) // 2 left_points = points[:mid] right_points = points[mid:] left_min_dist = closest_pair(left_points) right_min_dist = closest_pair(right_points) // 求解中间区域的最近对距离 min_dist = min(left_min_dist, right_min_dist) strip_points = [p for p in points if abs(p.x - points[mid].x) < min_dist] strip_points.sort(key=lambda p: p.y) for i in range(len(strip_points)): for j in range(i+1, min(i+8, len(strip_points))): dist = distance(strip_points[i], strip_points[j]) min_dist = min(min_dist, dist) return min_dist ``` 其中,brute_force是暴力求解最近对距离的函数,distance是计算两个之间距离的函数。时间复杂度约为O(N log N)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值