c++凸包学习笔记

凸包简介

凸包就是把一些点的最外围那一圈的点围起来。

就像是桌上有一圈钉子,用一根橡皮筋套在最外围,然后收缩橡皮筋。形成的图形就是这一圈点的凸包。

常见的凸包算法有两种。\(\mathrm{Graham}\)算法和\(\mathrm{Andrew}\)算法。

Anderw

通过按照\(x\)坐标或\(y\)坐标从小到大排序,按照顺序来建边,加入栈中。建完看看能不能弹出上一条边。\(\mathrm{Andrew}\)算法一次能确定一个凸包的一半。

这里拿按\(x\)坐标排序,确定上凸壳

这是一张图,结点编号已按\(x\)坐标排序。

现在连接\(1,2\)结点

然后连接\(2,3\)结点。

由于边\(1,3\)高于\(1,2,3\)的连边,所以可以断掉连接结点\(1,2\)的边,连接\(1,3\)

然后断掉边\(1,3\),连接\(1,4\)

连接\(4,5\)

断掉边\(4,5\),连接\(4,6\)

断掉边\(4,6\),连接\(4,7\)

这里发现边\(1,4\)和边\(4,7\)斜率相同,所以优先选择较远的点,这样可以让凸包中的结点变少。

所以断掉边\(1,4\),连接\(1,7\)

连接\(1,8\)

然后一个凸包的上凸壳也就求完了。用同样的方法可以求出下凸壳。

后两张图变大是因为我不小心把画图大小搞乱了。所以截图就那么大。

回归正题。从这里可以看出,弹点的时候可以弹多个。

要注意的是两次进行\(\mathrm{Andrew}\)的时候\(x\)坐标最小和最大的结点被记录了两次,所以只能取一次,要注意一下。

Graham

先找到一个基准点。(一般以\(x\)坐标和\(y\)坐标最小的点作为基准点。如果没有,\(x\)最小\(y\)最小都行)

然后做一条和\(x\)轴平行的直线。按照这条直线和与基准点连边形成的角度排序。再连边。

这是一张图

然后我们以\(y\)坐标最小的点为基准点,按角度排序

连接\(1,2\)

连接\(2,3\)

连接\(3,4\)
7H0mqq.png
因为\(3,5\)叉积更小,所以断掉\(3,4\),连接\(3,5\)
7H0HZb.png
连接\(5,6\)
7H0Jk1.png
断掉\(5,6\),连接\(5,7\)
7H0oiL.png
连接\(7,8\)
7H0EVx.png
连接\(8,1\)
7H0dnM.png

\(\mathrm{Graham}\)算法最大的区别是一次性就可以求完一整个凸包,但是这两个算法的时间复杂度是一样的。求法也类似

\(\mathrm{Graham}\)算法求到最后的时候要再连上边\(n,1\)

总之,排序是关键

例题

洛谷P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包

这个就是求凸包的周长。只要把凸包中的点存到数组里,然后可以用两点之间距离公式求出任意两点之间的距离。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node
{
	double x,y;
}a[N],s[N],s2[N];
node root;
double cross(node a,node b)//叉积    顺负逆正
{
	return a.x*b.y-a.y*b.x;
}
double check(node a1,node a2,node b1,node b2)
{
	return cross((node){a2.x-a1.x,a2.y-a1.y},(node){b2.x-b1.x,b2.y-b1.y});
}
double len(node a,node b)// 求两点距离。
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(node a,node b)//Graham
{
	double ck=check(root,a,root,b);
	if(ck>0)return 1;
	if(ck==0&&len((node){0,0},a)<len((node){0,0},b))//这里要把离基准点距离远的放前,这样枚举到后面就可以把前面的点弹掉
		return 1;
	return 0;
}
bool cmp2(node a,node b)//Andrew 
{
	if(a.x==b.x)return a.y<b.y;
	return a.x<b.x;
} 
int n;
void Graham()
{
	for(int i=1;i<=n;i++)//找基准点
	{
		if(i!=1&&a[i].x<a[1].x)//把想x,y最小放到最前。
		{
			swap(a[1],a[i]); 
		} 
		if(i!=1&&a[i].x==a[1].x&&a[i].y<a[1].y)
		{
			swap(a[1],a[i]); 
		}
	}
	root=a[1];//root为基准点。
	
	int top=1;
	sort(a+2,a+1+n,cmp);
	s[1]=a[1];
	for(int i=2;i<=n;i++)
	{
		while(top>1&&check(s[top-1],s[top],s[top],a[i])<=0)
			top--;
		s[++top]=a[i];
	}
	s[top+1]=a[1];//算周长,栈顶再算一遍
	double ans=0;
	for(int i=1;i<=top;i++)
	{
		ans+=len(s[i],s[i+1]);
	}
	printf("%.2Lf",ans);
}

void Andrew()
{
	sort(a+1,a+1+n,cmp2);
	int top=1;
	s[1]=a[1];
	for(int i=2;i<=n;i++)
	{
		while(top>1&&check(s[top-1],s[top],s[top],a[i])<=0)
			top--;
		s[++top]=a[i];
	}

	s2[1]=a[n];
	int top2=1;
	for(int i=n;i>=1;i--)
	{
		while(top2>1&&check(s2[top2-1],s2[top2],s2[top2],a[i])<=0)
			top2--;
		s2[++top2]=a[i];
	}
	double ans=0;	
	for(int i=1;i<top;i++)
	{
		ans+=len(s[i],s[i+1]);
	}
	for(int i=1;i<top2;i++)
	{
		ans+=len(s2[i],s2[i+1]);
	}
	printf("%.2Lf",ans);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].x>>a[i].y;
	}
//	Graham(); 
	Andrew();
	return 0;
}
posted @ 2025-10-15 17:36  NumLuck  阅读(17)  评论(0)    收藏  举报