空间平面计算集合(三):凸包概念+Graham扫描算法/旋转卡壳+相应题型

抽象解释:在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造(来自百度百科);

简单理解:就是一个点集中最外围的部分点,这些最外围的点的集合,就是包围原点集的最小凸多边形的顶点组成的集合,称为原点集的凸包。下图蓝线相连的5个点即为凸包
在这里插入图片描述

Graham算法

寻找凸包的算法有很多种,Graham Scan 算法是一种十分简单高效的二维凸包算法,能够在 O(nlogn) 的时间内找到凸包

1.将给定的集合的点按X坐标升序排序,X相同则按Y升序排序;

2.创建凸包上部:
将排序后的点按从小到大顺序加入凸包A,若新加入的点使凸包A不再是凸多边形,则逆序删除之前加入的点,直到重新变成凸多边形;

举例说明:首先加入两个点A,B
在这里插入图片描述
然后插入第三个点 C,并计算向量 AB × BC的向量积,却发现向量积系数小于(等于)0,也就是说向量 BC 在 AB 的顺时针方向上,(对叉乘而言,逆时针方向为正),显然当BC在AB的顺时针方向时,B的存在使得下边界不凸,所以去掉B。
在这里插入图片描述
按照这样的方法依次扫描,找出下边界,再举一个例子
在这里插入图片描述

3.创建凸包下部:
将排序后的点按从大到小顺序加入凸包B,若新加入的点使凸包B不再是凸多边形,则逆序删除之前加入的点,直到重新变成凸多边形;判断方法与上壳一致

4.合并上下双壳,得到最终结果。

模板

struct point {
	double x, y;

	point(ll a = 0, ll b = 0) { x = a, y = b; }
	point operator +(const point p) { return point(x + p.x, y + p.y); }
	point operator -(const point p) { return point(x - p.x, y - p.y); }
	double operator*(const point p) { return x * p.x + y * p.y; }//内积
	double mul(const point p) { return x * p.y - p.x*y; }//叉乘
};
point ps[Max];

bool cmp(point p, point q) {
	if (p.x == q.x) return p.y < q.y;
	else return p.x < q.x;
}
//求凸包
vector<point> convexHull(int n) {
	sort(ps, ps + n, cmp);
	int k = 0; //凸包的顶点数目
	vector<point> qs(n * 2);//构造中的凸包
	//构造中的凸包下侧
	for (int i = 0; i < n; i++) {
		//(qs[k - 1] - qs[k - 2]):点k-2到k-1的向量
		//(ps[i] - qs[k - 1]):当前判断的点i到k-1的向量
		//mul:叉乘,结果为负说明k-2,k-1,i不能构成一个凸集
		while (k > 1 && (qs[k - 1] - qs[k - 2]).mul(ps[i] - qs[k - 1]) <= 0)k--;
		qs[k++] = ps[i];
	}
	for (int i = n - 2, t = k; i >= 0; i--) {
		//n-2:最右侧的点肯定在凸集内部
		while (k > t && (qs[k - 1] - qs[k - 2]).mul(ps[i] - qs[k - 1]) <= 0)k--;
		qs[k++] = ps[i];
	}
	qs.resize(k - 1);
	return qs;
}

还有一篇写的很好的博文

旋转卡壳Rotating calipers

所谓的旋转卡壳法,就是在凸包上旋转扫描的方法,一般旋转卡壳是用来求取凸集的直径。而凸包的直径,往往可以用来求平面上的最远点对之类的问题。

适用场景

(1)计算距离

  • 凸多边形直径
  • 凸多边形宽
  • 凸多边形间最大距离
  • 凸多边形间最小距离

(2)外接矩形

  • 最小面积外接矩形
  • 最小周长外接矩形

(3)三角剖分

  • 洋葱三角剖分
  • 螺旋三角剖分
  • 四边形剖分

(4)凸多边形属性

  • 合并凸包
  • 找共切线
  • 凸多边形交
  • 临界切线
  • 凸多边形矢量和

定义

对踵点对: 如果两个点 p 和 q 是凸多边形边上的点。而且他们在两条平行切线上, 那么他们就形成了一个对踵点对。 以下是三种不同的对踵点对,注意一对不一定只有两个
在这里插入图片描述
先写一个计算距离的,其他的旋转思路大致相同。假设最远点对是 p p p q q q,那么 p p p就是点集中 ( p − q ) (p-q) (pq)方向最远的点,而 q q q是点集 ( q − p ) (q-p) qp方向最远的点。因此,可以按照逆时针逐渐改变方向,同时枚举出所有对于某个方向上最远的点对。那么最远点对一定也包含于其中。在逐渐改变方向的过程中,对踵点对只有在方向等于凸包某条边的法线方向时发生变化,此时点将向凸包上对应的相邻点移动。令方向逆时针旋转一周,那么对踵点对也在凸包上转了一周,这样就可以在凸包顶点数的线性时间内求得最远点对。
在这里插入图片描述

void Rotation() {
	vector<point> qs = convexHull(N);
	int n = qs.size();
	if (n == 2) { cout << int(dist(qs[0], qs[1])) << endl; return; }//处理凸包退化的场面
	int i = 0, j = 0;//某个方向上的接踵点对
	//求出x轴方向上的接踵点对,j为最左点,i为最右点
	//为什么不用排序取收尾?这样做只需要O(n)
	for (ll k = 0; k < n; k++) {
		if (!cmp(qs[i], qs[k]))i = k;
		if (cmp(qs[j], qs[k]))j = k;
	}
	double res = 0;
	ll si = i, sj = j;
	//i--j,如果i转到了sj的位置,那么i转了180了,同理j转到si的位置也就180了
	while (i != sj || j != si) {//将方向逐步旋转180,注意条件
		res = max(res, dist(qs[i], qs[j]));
		// 判断先转到边i-(i+l)的法线方向还是边j-(j+l)的法线方向
		if ((qs[(i + 1) % n] - qs[i]).mul(qs[(j + 1) % n] - qs[j]) < 0)
			i = (i + 1) % n;// 先转到边i-(i+l)的法线方向
		else
			j = (j + 1) % n;// 先转到边j-(j+l)的法线方向
	}
	ll r = res;
	cout << r << endl;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值