美文网首页
分治法求解凸包问题

分治法求解凸包问题

作者: bazinga_dmc | 来源:发表于2021-12-13 21:43 被阅读0次

求解二维凸包问题,时间复杂度为O(nlogn),即先通过点集中每个点的x值将点集划分为左右两部分,分别求解其凸包,再通过two finger方法将两个凸包合并,时间复杂度类似于合并排序,完整代码如下,包含一个测试用例,可直接运行(具体的算法讲解可看OCW 6.046J第2集)。

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>

using namespace std;

//二维点
struct point2d{
    double x;
    double y;
    point2d(double _x, double _y): x(_x), y(_y){}
    operator double(){return x;}
};

//前向声明
list<point2d> two_finger(list<point2d>& left, list<point2d>& right);

//解决convex hull问题的主要函数,利用divide and conquer
//pts中的点按照x轴从小到大排列,链表中的点按顺时针排列
list<point2d> convex_hull(vector<point2d>& pts, int l, int r){
    if(r - l + 1 <= 3)//基本情况
        return list<point2d>(pts.begin() + l, pts.begin() + r + 1);
    int mid = (l + r) / 2;
    //左侧子问题的解
    auto left = convex_hull(pts, l, mid);
    //右侧子问题的解
    auto right = convex_hull(pts, mid + 1, r);
    //合并左右两侧子问题(采用视频中提到的two finger方法)
    return two_finger(left, right);
}


//计算p1和p2连线交于x的y
inline double y_intersect(const point2d& p1, const point2d& p2, double x){
    return p1.y + (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x);
}

//跳过end迭代器
list<point2d>::iterator mynext(list<point2d>::iterator it, list<point2d>& lst){
    ++it;
    if(it == lst.end()) ++it;
    return it;
}

//跳过end迭代器
list<point2d>::iterator myprev(list<point2d>::iterator it, list<point2d>& lst){
    --it;
    if(it == lst.end()) --it;
    return it;
}

//视频中的two finger算法
list<point2d> two_finger(list<point2d>& left, list<point2d>& right){
    //找到左侧凸包中x最大的点,即a1
    auto a1 = left.end();
    for(auto it = left.begin(); it != left.end(); ++it)
        if(a1 == left.end() || *it > *a1)
            a1 = it;
    //找到右侧凸包中x最小的点,即b1
    auto b1 = right.end();
    for(auto it = right.begin(); it != right.end(); ++it)
        if(b1 == right.end() || *it < *b1)
            b1 = it;
    //分割线对应的x
    double x = (a1->x + b1->x) / 2;
    //利用two finger找到上切线和下切线
    //上切线
    auto ai = a1;
    auto bj = b1;
    while(y_intersect(*ai, *mynext(bj, right), x) > y_intersect(*ai, *bj, x) ||
          y_intersect(*myprev(ai, left), *bj, x) > y_intersect(*ai, *bj, x)){
        if(y_intersect(*ai, *mynext(bj, right), x) > y_intersect(*ai, *bj, x))
            bj = mynext(bj, right);
        else
            ai = myprev(ai, left);
    }
    //下切线
    auto ak = a1;
    auto bm = b1;
    while(y_intersect(*ak, *myprev(bm, right), x) < y_intersect(*ak, *bm, x) ||
          y_intersect(*mynext(ak, left), *bm, x) < y_intersect(*ak, *bm, x)){
        if(y_intersect(*ak, *myprev(bm, right), x) < y_intersect(*ak, *bm, x))
            bm = myprev(bm, right);
        else
            ak = mynext(ak, left);
    }
    //按顺时针找出边界上的点
    list<point2d> rslt;
    //ai->bj
    rslt.push_back(*ai);
    //右侧(循环遍历)
    for(auto it = bj; it != bm; ++it){
        if(it == right.end()){
            it = right.begin();
            if(it == bm) break;
        }
        rslt.push_back(*it);
    }
    //bm->ak
    rslt.push_back(*bm);
    //左侧(循环遍历)
    for(auto it = ak; it != ai; ++it){
        if(it == left.end()){
            it = left.begin();
            if(it == ai) break;
        }
        rslt.push_back(*it);
    }
    return rslt;
}

void print(ostream& os, list<point2d>&& lst){
    os << "pts: ";
    for(auto pt: lst)
        os << "{" << pt.x << ", " << pt.y << "} ";
    os << endl;
}

//总的时间复杂度为O(nlogn)利用主定理很容易证明
//由于two finger算法时间复杂度为O(n)(可类比为
//merge sort中的merge过程)所以convex hull时间
//复杂度和merge sort一致,均为O(nlogn)
int main(){
    vector<point2d> test_point = {{1, 1}, {2, 2}, {3, 1.5},
                                  {4, 1.5}, {5, 2}, {6, 1},
                                  {3.5, 4}};

    //按照x对点进行排序
    sort(test_point.begin(), test_point.end());

    //求解convex hull
    print(cout, convex_hull(test_point, 0, test_point.size() - 1));
}

相关文章

  • 分治法求解凸包问题

    求解二维凸包问题,时间复杂度为O(nlogn),即先通过点集中每个点的x值将点集划分为左右两部分,分别求解其凸包,...

  • 《python算法教程》Day11 - 分治法求解平面凸包问题

    这是《python算法教程》的第11篇读书笔记,笔记主要内容是使用分治法求解凸包。 平面凸包问题简介 在一个平面点...

  • 分治策略

    求解递归式方法 最大子数组问题 分治策略 分治法流程 伪代码 C++实现 线性解 流程 代入法求解递归式 递归树法...

  • 动态规划

    动态规划用于求解多阶段决策最优解的问题。 其基本思想与分治法类似,也是将求解问题分解为多个子问题,与分治法不同的是...

  • 分治法

    分治法求解的思想是将整个问题分成若干小问题后分而治之。通常,由分治法多得到的小问题与原问题具有相同的类型。并且在求...

  • 数据结构与算法--分治法、归并排序

    分治法 分治法的思想是:将原问题分解成若干个规模较小但是与原问题类似的问题,递归地求解这些子问题,然后再合并这些子...

  • 0x01 分治法

    1、分治策略分治法是采用分而治之,逐个解决的策略。孙子兵法曰:凡治众如寡者,分数是也。 采用分治求解的问题必须具有...

  • 拉格朗日乘子法和KKT条件

    拉格朗日乘子法 要解决的问题 拉格朗日乘子法要解决的就是有等式限制条件的凸优化问题。形式如下: 求解方式 例如: ...

  • 《算法导论》--动态规划

    1. 概述 动态规划与分治法相似,都是通过组合子问题来求解原问题。区别在于,分治法将问题划分为互不相交的子问题,递...

  • 算法设计技巧: 分治法 (Divide & Conquer)

    分治法是一种非常通用的算法设计技巧. 在很多实际问题中, 相比直接求解, 分治法往往能显著降低算法的计算复杂度. ...

网友评论

      本文标题:分治法求解凸包问题

      本文链接:https://www.haomeiwen.com/subject/bwusfrtx.html