凸包算法学习笔记
Graham 算法
- 先选一个y最小的点p(y相同选x最小)
- 然后将其他点以p为原点进行极角排序
- 从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算法
- 将所有点按x第一关键字,y第二关键字从小到大排序。
- 从最左边的点开始向右遍历点并维护单调栈,如果栈顶两个元素和新点的叉积<0则弹出,这样会形成一个下凸包
- 同样的从右往左继续维护单调栈,形成上凸包
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算法更优