凸包算法学习笔记

Graham 算法

  1. 先选一个y最小的点p(y相同选x最小)
  2. 然后将其他点以p为原点进行极角排序
  3. 从p点开始维护一个单调栈,如果栈顶两个元素和新加入点的叉积小于0就弹出。
Point tbBottom;
bool cmpTB(Point a,Point b) {//上半平面极角排序;
    double x=xmult(tbBottom,a,b);
    if(sgn(x)>0||(sgn(x)==0&&dist(a,tbBottom)<dist(b,tbBottom))) return 1;//距离近的放前面
    return 0;
}
vector<Point> getTB(vector<Point> p){//n>=3
    int n=p.size();
    int minp=0;
    for(int i=1;i<n;i++){
        if(sgn(p[i].y-p[minp].y)==-1 || (sgn(p[i].y-p[minp].y)==0&&sgn(p[i].x-p[minp].x)==-1)){//选左下角的第一个点
            minp=i;
        }
    }
    swap(p[0],p[minp]);//使第一个点y最小
    tbBottom=p[0];
    sort(p.begin()+1,p.end(),cmpTB);
    vector<Point>tb;
    tb.push_back(p[0]);
    tb.push_back(p[1]);
    for(int i=2;i<n;i++){
        while(tb.size()>=2 && sgn(xmult(tb[tb.size()-2],tb[tb.size()-1],p[i]))<=0){
            tb.pop_back();
        }
        tb.push_back(p[i]);
    }
    return tb;
}

Andrew算法

  1. 将所有点按x第一关键字,y第二关键字从小到大排序。
  2. 从最左边的点开始向右遍历点并维护单调栈,如果栈顶两个元素和新点的叉积<0则弹出,这样会形成一个下凸包
  3. 同样的从右往左继续维护单调栈,形成上凸包
bool cmpA(Point a,Point b){
    return a.x<b.x||(a.x==b.x && a.y<b.y);
}
vector<Point> Andrew(vector<Point> p) { //Andrew算法求凸包(求上链与下链):p是点数组,ch是凸包顶点,返回顶点数
    //输入不能有重复点,若要凸包边上没有输入点,将两个<=改为<
    sort(p.begin(),p.end(),cmpA);
    vector<Point>tb;
    for(int i=0; i<p.size(); i++) {//Cross(ans[top-1]-ans[top-2],p[i]-ans[top-2])
        while(tb.size()>=2&&sgn(xmult(tb[tb.size()-2],tb[tb.size()-1],p[i]))<=0)tb.pop_back();
        tb.push_back(p[i]);
    }
    int temp=tb.size();
    for(int i=p.size()-2; i>=0; i--) {
        while(tb.size()>temp&&sgn(xmult(tb[tb.size()-2],tb[tb.size()-1],p[i]))<=0)tb.pop_back();
        tb.push_back(p[i]);
    }
    if(p.size()>1)tb.pop_back();
    return tb;
}

总结

Andrew算法代码更短,排序更快(比较时不需要计算叉积),而且Andrew算法不容易出现精度产生的误差问题(这个很重要)。从各方面来看都是Andrew算法更优

posted @ 2021-03-17 10:42  UCPRER  阅读(150)  评论(0编辑  收藏  举报