Home 凸包 —— Convex Hull
Post
Cancel

凸包 —— Convex Hull

在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。

前置知识

叉积(Cross product): 用于计算角度,a x b = a * b * sinθ

极角排序: 在平面内取一个定点O,叫极点,引一条射线Ox,叫做极轴,再选定一个长度单位和角度的正方向(通常取逆时针方向)。对于平面内任何一点M,用ρ表示线段OM的长度(有时也用r表示),θ表示从Ox到OM的角度,ρ叫做点M的极径,θ叫做点M的极角,有序数对 (ρ,θ)就叫点M的极坐标。那么给定平面上的一些点,把它们按照一个选定的中心点排成顺(逆)时针。

Basic Code:

struct point {
    double x,y;
};
double cross(double x1,double y1,double x2,double y2){ //计算叉积
    return (x1*y2-x2*y1);
}
double count(point a,point b,point c) { //计算极角
    return cross((b.x-a.x),(b.y-a.y),(c.x-a.x),(c.y-a.y));
}

极角排序 —— atan2

关于atan2()函数:在C语言的math.h或C++中的cmath中有两个求反正切的函数atan(double x)与atan2(double y,double x) 他们返回的值是弧度要转化为角度再自己处理下。

前者接受的是一个正切值(直线的斜率)得到夹角,但是由于正切的规律性本可以有两个角度的但它却只返回一个,因为atan的值域是从-90~90 也就是它只处理一四象限,所以一般不用它。

第二个atan2(double y,double x) 其中y代表已知点的Y坐标,同理x ,返回值是此点与远点连线与x轴正方向的夹角,这样它就可以处理四个象限的任意情况了,它的值域相应的也就是-180~180了。

bool cmp1(point a,point b) {
    if(atan2(a.y,a.x)!=atan2(b.y,b.x))
        return atan2(a.y,a.x)<atan2(b.y,b.x);
    else return a.x<b.x;
}

极角排序 —— Cross Product

关于叉积:叉积=0是指两向量平行(重合);叉积>0,则向量a在向量b的顺时针方向(粗略的理解为在a在b的下方);叉积<0,则向量a在向量b的逆时针方向(粗略的理解为在a在b的上方)。

bool cmp2(point a,point b) {
    point c;//原点
    c.x = 0;
    c.y = 0;
    if(count(c,a,b)==0) return a.x<b.x;
    else return count(c,a,b)>0;
}

关于第一种方法,利用atan2排序,与利用叉积排序的主要区别在精度和时间上。

具体对比:时间:相较于计算叉积,利用atan2时间快,这个时间会快一点

Graham扫描法

静态凸包求解,时间复杂度:O(nlog(n))

算法步骤:

  1. 首先先将最左下节点拿出,显然这个点一定是凸包结点,然后以这个点为极点,对点集作极角排序,角度一样的按照到极点距离从大到小排序,显然这时候最后一个节点,极点,第一个节点都会在凸包内
  2. 将排序后的前两个点入栈,其中栈中保存着凸包结点
  3. 构建凸包的过程可以总结为将栈顶和栈顶上一位元素所构成的向量 与当前遍历的点和栈顶上一位元素所构成的向量作叉积,如果叉积大于等于0,就将当前遍历节点入栈,否则如果小于0,弹出栈顶,遍历节点入栈
  4. 直到我们遍历到节点为一开始排序的最后一个节点,节点入栈,程序结束,此时凸包结果就在栈中

Code

#include<bits/stdc++.h>
#define bug(a) (cout<<'*'<<a<<endl)
#define bugg(a,b) (cout<<'*'<<a<<' '<<b<<endl)
#define buggg(a,b,c) (cout<<'*'<<a<<' '<<b<<' '<<c<<endl)
using namespace std;
typedef long long ll;
struct point
{
	double x, y;
	double pos;
	point operator+(point a) { return point{ x + a.x,y + a.y }; }
	point operator-(point a) { return point{ x - a.x,y - a.y }; }
	double operator*(point a) { return  x *a.x+y * a.y; }
	point operator*(ll num) { return point{ x * num,y * num }; }
	double operator^(point a) { return x * a.y - y * a.x; }
};
point p[100009];
bool cmp(point a, point b)
{
	if (a.x != b.x && a.y != b.y)
		return a.pos < b.pos;
	else
		if (a.x == b.x)
			return a.y > b.y;
		else
			return a.x > b.x;
}
double dis(point a, point b)
{
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int st[100009];
int main()
{
	int n, i, j;
	cin >> n;
	for (i = 1; i <= n; i++)
	{
		cin >> p[i].x >> p[i].y;
	}
	for (i = 1; i <= n; i++)
	{
		if (p[1].x > p[i].x || (p[1].x == p[i].x && p[1].y > p[i].y))
			swap(p[i], p[1]);
	}
	double x=p[1].x, y=p[1].y;
	for (i = 1; i <= n; i++)
	{
		p[i].x -= x;
		p[i].y -= y;
		p[i].pos = atan2(p[i].x, p[i].y);
	}
	sort(p + 2, p + 1 + n, cmp);
	int top = 0;
	st[++top] = 1, st[++top] = 2;
	for (i = 3; i <= n; i++)
	{
		while ((((p[st[top]] - p[st[top - 1]]) ^ (p[i] - p[st[top - 1]])) > 0) && top > 2)
			top--;
		st[++top] = i;
	}
	st[++top] = st[1];
	double ans = 0;
	for (i = 1; i <= top; i++)
	{
		ans += dis(p[st[i]], p[st[i - 1]]);
	}
	printf("%.2lf\n", ans);
	return 0;
}

模版题

https://www.luogu.com.cn/problem/P2742

This post is licensed under CC BY 4.0 by the author.

Monte Carlo Method

模拟退火 —— Simulated Annealing

Comments powered by Disqus.