Garham算法

计算几何(凸包)

凸包的定义

点集Q的凸包是一个最小的凸多边形P,满足Q中每个点都在P的边界或者内部。通俗来讲,就是在一个平面上钉上n个钉子,然后用一根橡皮筋将所有钉子都围住,最后橡皮筋所构成的一个凸多边形。(下面主要讲解用Graham算法求凸包)

Graham算法

  1. 找到平面上最靠近左下角的点(设为 S 0 S_0 S0)
  2. 将其他点按极角(以 S 0 S_0 S0为顶点向水平向右方向引一条射线,其他点与 S 0 S_0 S0连线和该水平射线所成角即为该点的极角)排序,极角可用C++自带的atan2直接得到。
  3. 单调栈思想:将点 S 0 S_0 S0和排序后的第一个点入栈(这里设栈顶第一个点为 S a S_a Sa,第二个为 S b S_b Sb),后面的点 S i S_i Si入栈时进行判断,如果向量 S a S_a Sa S i S_i Si是否在向量的 S b S_b Sb S a S_a Sa逆时针方向,如果在就让 S i S_i Si入栈,然后继续判断下一个点,否则 S a S_a Sa出栈,然后循环此过程,直到最后一个点。最后栈中所有点按顺序连起来就构成了这个点集的凸包。
    判断向量BC是否在向量AB的逆时针方向,我们要用到叉积,叉积小于0表示在顺时针,大于0则在逆时针(在纸上自己画一画就能看出来了)
    洛谷凸包模板题
#include<iostream>
#include<algorithm>
#include<cmath>

#define Vector point
using namespace std;
typedef double db;
const db eps = 1e-6, pi = acos(-1.0);
const int N = 1e5 + 5;
int n, cnt;
int fcmp(db x)	//判断一个小数是否大于0,由于精度问题不能直接判断,需要与eps进行比较
{
	if(fabs(x) < eps) return 0; // x = 0
	else if(x > 0) return 1; // x > 0
	else return -1; // x < 0
}
struct point
{
	db x, y;
	point() {}
	point(db x, db y): x(x), y(y){}
	Vector operator + (const Vector &A) {return Vector(x + A.x, y + A.y);}
	Vector operator - (const Vector &A) {return Vector(x - A.x, y - A.y);}
	Vector operator * (const db &A) {return point(x * A, y * A);}
	Vector operator / (const db &A) {return point(x / A, y / A);}
	bool operator == (const Vector &A) {return !fcmp(x - A.x) && !fcmp(y - A.y);}
	point rotate_90s() {return point(y, -x);}
	point rotate_90n() {return point(-y, x);}
}a[N], s[N];
db dot(const Vector &A, const Vector &B) // 点积 
{
	return A.x * B.x + A.y * B.y; 
}
db cross(const Vector &A, const Vector &B) // 叉积 
{
	return A.x * B.y - A.y * B.x;
}
db dist(const point &A, const point &B) // 距离 
{
	return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
}
bool cmp(const point A, const point B)	//排序函数
{
	if(atan2(A.y - a[0].y, A.x - a[0].x) != atan2(B.y - a[0].y, B.x - a[0].x))
		return atan2(A.y - a[0].y, A.x - a[0].x) < atan2(B.y - a[0].y, B.x - a[0].x);
	return dist(a[0], A) < dist(a[0], B);	//如果极角相同则按距离从小到大排序
}
bool check(point &A, point &B)	//判断两向量的叉积是否小于0
{
	db C = cross(A - a[0], B - a[0]);
	if(C > 0) return 1;
	else if(fcmp(C) == 0) return 1;
	else return 0;
}
int main()
{
	cin >> n;
	for(int i = 0; i < n; i++)
	{
		cin >> a[i].x >> a[i].y;
		if(i && a[i].y < a[0].y)
		{
			point t = {a[i].x, a[i].y};
			a[i] = a[0]; a[0] = t;
		}
		else if(a[i].y == a[0].y && a[i].x < a[0].x)
		{
			point t = {a[i].x, a[i].y};
			a[i] = a[0]; a[0] = t;
		}
	}
	sort(a + 1, a + n, cmp);
	s[0] = a[0];	//用数组模拟栈,更方便
	for(int i = 1; i < n; i++)
	{
		while(cnt && fcmp(cross(s[cnt] - s[cnt - 1], a[i] - s[cnt - 1])) <= 0) cnt--;	//叉积小于等于0就出栈
		s[++cnt] = a[i];	//进栈
	}
	s[++cnt] = a[0];
	db res = 0;
	for(int i = 0; i < cnt; i++)
		res += dist(s[i], s[i + 1]);	//求凸包所围成的周长
	printf("%.2lf", res);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值