凸包的简单基操(欢迎入坑凸包!!!)

前言:

  这是第一次接触凸包,希望把自己学到的东西记录下来,如果认为本文还有待修改的地方,欢迎留言!!!

凸包的前置知识(不难,请自行百度):

  1. 向量叉乘
  2. 平面上离散点质心的求法
  3. 凸包:二维平面上能够恰好将所有点包围起来的最小长度的图形

凸包的基本操作一:求凸包的周长

  首先问下大家如果给大家一个多边形,你们会怎么求他的周长?(就比如下面这幅图)
在这里插入图片描述
  它的周长 C = ∣ A B ∣ + ∣ B C ∣ + ∣ C D ∣ + ∣ D A ∣ C=|AB|+|BC|+|CD|+|DA| C=AB+BC+CD+DA
  为了方便我们肯定是按照点的顺时钟方向或逆时针方向来求,而不是一段一段
  所以,我们如果为了求凸包的周长,首先要将其输入的点按顺时钟排序或者逆时针顺序(代码中是按逆时针排序

排序规则:
  1. 找出给定点中最左下角的那个点
  2. 以该点为极点,将剩下的点按照极角从小到大排序(逆时针)
struct point {
	double x, y;
	point friend operator - (point a, point b) {
		return {a.x - b.x, a.y - b.y};
	}//重新定义-为向量间的减法
}p[105], s[105];
double X(point a, point b) {//向量间的叉乘
	return a.x * b.y - a.y * b.x;
}
bool cmp(point & a, point & b) {
	double x = X(a - p[1], b - p[1]);
	//如果叉乘大于0,说明b-p[1]向量在a-p[1]的逆时钟方向
	if(x > 0) return true;//以p[1]为极点,按照极角从小到大排序
	if(x == 0 && dis(a, p[1]) < dis(b, p[1])) return true;//角度相同按距离从小到大排序 
	return false; 
}
找出外凸包:

  找出外凸包上的点,首先要判断哪些点符合条件,比如说下面这幅图上的点是符合条件的
在这里插入图片描述
   A C AC AC能够逆时针转到 B C BC BC说明这三个点符合
—————————————————————————————————
  下面这幅图上的连边(ABCDE连边)是不符合条件的
在这里插入图片描述
  因为 C E CE CE是顺时钟转到 D E DE DE(沿着小于等于180°的角,并且起始点移到同一个点)
  那么对于不符合的情况,要一直回退,直到退到只剩一个点,或则满足情况,才能加上该点(上图的情况回退到C点即可,CE相连既符合情况)

	s[1] = p[1], s[2] = p[2];//s[2]也会被替换掉,但s[1]不会,因为p[1]为图中最左下角的点,其必然会在凸包上 
	int t = 2;
	for(int i = 3; i <= N; i++) {
		while(t > 1 && multi(s[t - 1], s[t], p[i]) <= 0) t--;//只保留最外面的那些点 
		s[++t] = p[i];
	}
计算周长:
double dis(point a, point b) {
	point c = a - b;
	return sqrt(c.x * c.x + c.y * c.y);
}
for(int i = 1; i <= t; i++) sum += dis(s[i], s[i % t + 1]);

完整代码:(HDU-1392)
//求凸包周长 
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int N;
struct point {
	double x, y;
	point friend operator - (point a, point b) {
		return {a.x - b.x, a.y - b.y};
	}
}p[105], s[105];
double dis(point a, point b) {
	point c = a - b;
	return sqrt(c.x * c.x + c.y * c.y);
}
double X(point a, point b) {
	return a.x * b.y - a.y * b.x;
}
bool cmp(point & a, point & b) {
	double x = X(a - p[1], b - p[1]);
	if(x > 0) return true;//以p[1]为极点,按照极角从小到大排序
	if(x == 0 && dis(a, p[1]) < dis(b, p[1])) return true;//角度相同按距离从小到大排序 
	return false; 
}
double multi(const point & p1, const point & p2, const point & p3) {
	return 	X(p2 - p1, p3 - p1);
}
int main() {
	while(scanf("%d", &N) && N) {
		for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
		if(N == 1) printf("0.00\n");
		else if(N == 2) printf("%.2lf\n", dis(p[1], p[2]));
		else {
			int k = 1;
			for(int i = 2; i <= N; i++)
			    if(p[i].y < p[k].y || p[i].y == p[k].y && p[i].x < p[k].x) k = i; 
			swap(p[1], p[k]);
			sort(p + 2, p + N + 1, cmp);
			s[1] = p[1], s[2] = p[2];//s[2]也会被替换掉,但s[1]不会,因为p[1]为图中最左下角的点,其必然会在凸包上 
			int t = 2;
			for(int i = 3; i <= N; i++) {
				while(t > 1 && multi(s[t - 1], s[t], p[i]) <= 0) t--;//只保留最外面的那些点 
				s[++t] = p[i];
			}
			double sum = 0;
			for(int i = 1; i <= t; i++) sum += dis(s[i], s[i % t + 1]);
			printf("%.2lf\n", sum);
		}
	}
	return 0;
} 

ps:接下来虽然是多边形的一些问题,但是和凸包中计算重复的部分有很多,于是放在这里

凸包的基本操作二:求任意多边形的面积

  首先直到两向量 A B AB AB, B C BC BC的叉乘即是他们所构成的平行四边形面积,那么除以2,便是 A B C ABC ABC三点构成的三角形面积
在这里插入图片描述
  因为叉乘是有正负的,于是会有如下的定理,按照顺(逆)时钟相邻两点的叉乘全部相加,最后取绝对值÷2,即为多边形面积
  接下来让我们观察这幅图
在这里插入图片描述
ps1:箭头偷懒没有打出,×为向量之间的叉乘

  1. ( O A × O B ) / 2 (OA×OB )/ 2 OA×OB/2多算出不属于多边形的面积 -SOAB(叉乘有正负)
  2. ( O B × O C ) / 2 (OB×OC)/2 OB×OC/2多算出不属于多边形的面积 +SOBAE
  3. ( O C × O D ) / 2 (OC×OD)/2 OC×OD/2多算出不属于多边形的面积 +SOED
  4. ( O D × O A ) / 2 (OD×OA)/2 OD×OA/2多算出不属于多边形的面积 -SODA

当这些多余的面积,保留正负相加之后,会刚好抵消,因此这些向量叉乘相加起来除二即为多边形面积

完整代码:(HDU-2036)
//求任意多边形面积
//因为本题是顺时针或逆时针给出,所以无需再排序
//如果不是那么就要选定一个最左下角的点,然后按顺时钟排序 
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int N;
struct point {
	double x, y;
}p[105];
int main() {
	while(scanf("%d", &N) && N) {
		for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
		double sum = 0;
		for(int i = 1; i <= N; i++) sum += p[i].x * p[i % N + 1].y - p[i].y * p[i % N + 1].x;
		sum = fabs(sum / 2.0);
		printf("%.1lf\n", sum);
	}
	return 0;
} 

凸包的基本操作三:求任意多边形的重心

精度不高的算法:
  在这里插入图片描述
精度高的算法:
  首先将 n n n边形分成 n − 2 n-2 n2个三角形,如图的分法
在这里插入图片描述
  这里就需要运用到刚刚的前置知识2,将原来是质量的地方改为面积即可(ps:求和符号中的 x x x为分成三角形的重心, s s s即为分成三角形的面积)
在这里插入图片描述

完整代码:(HDU-1115)
#include<bits/stdc++.h>
using namespace std;
struct point {
	double x, y;
    point friend operator -(point a, point b) {//对-进行重载
        return {a.x - b.x, a.y - b.y};
    }
    double friend operator *(point a,point b) {//对*进行重载  point*point 相当于X乘 
        return a.x * b.y - a.y * b.x;	
    }
}p[1000100];
int T, N;
int main() {
	scanf("%d", &T);
	while(T--) {
		scanf("%d", &N);
		for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
		double sum_x = 0, sum_y = 0, sum_area = 0, area = 0;
		for(int i = 2; i < N; i++) {
			area = (p[i] - p[1]) * (p[i + 1] - p[1]) / 2;//算面积
			sum_area += area;//算总面积
			sum_x += (p[i].x + p[i + 1].x + p[1].x) * area;
			sum_y += (p[i].y + p[i + 1].y + p[1].y) * area;
		}
		printf("%.2lf %.2lf\n", sum_x / sum_area / 3, sum_y / sum_area / 3);
		//除3是因为累加时没除3
	}
}

参考博客:

  1. https://www.cnblogs.com/jbelial/archive/2011/08/08/2131165.html
  2. https://www.cnblogs.com/wpbing/p/9456240.html
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值