Graham算法解决凸包问题

Graham算法解决凸包问题

模板题,题目来自洛谷如下。

圈奶牛【模板】二维凸包

题目简要描述

给定一些点,求凸包的周长。

输入格式

输入数据的第一行是一个整数。表示农夫约翰想要围住的放牧点的数目n。

2 2 2 到第 ( n + 1 ) (n + 1) (n+1) 行,每行两个实数,第 ( i + 1 ) (i + 1) (i+1) 行的实数 xi, yi 分别代表第 i i i 个放牧点的横纵坐标

输出格式

输出输出一行一个四舍五入保留两位小数的实数,代表围栏的长度。

输入输出样例
//输入
4
4 8
4 12
5 9.3
7 8

//输出
12.00    

Graham算法

该算法的大致思路为

​ 通过维持一个关于候选点的栈S来解决凸包问题。输入集合Q中的每个点都被压入栈一次,非凸包顶点的点最终被弹出栈。当算法终止时,栈中仅包含凸包顶点,且以逆时针的顺序出现在边界上。而该算法主要由排序扫描组成。(摘自《算法导论》)

主要过程

​ 首先是排序过程,易得纵坐标最小的值必定是凸包中的顶点,因此选择一个y值最小(如有相同选x最小)的点,记作P0。接着其余的点按极角的大小逆时针排序,编号P1至Pm

​ 接着是扫描过程,按上述排序过程遍历每个点,进行判断,如若该点更合适,则上一个点出栈,该点入栈,否则直接入栈。

图片说明:

​ 文字描述比较绕,下面给出图片描述。(图源来自《算法导论》)

图1
图2

代码实现:

#include<bits/stdc++.h>
#define maxn 8000005
using namespace std;
int n;
struct dot{
	double x;
	double y;
}p[maxn], s[maxn];  // p用以存所有点,s用以存凸包顶点 
double cp(dot a1, dot a2, dot b1, dot b2){ // 叉乘,a1,a2,b1,b2为四个点 
	return (a2.x - a1.x) * (b2.y - b1.y) - (b2.x - b1.x) * (a2.y - a1.y);
} 
double dis(dot p1, dot p2){ //求两点间距 
	return sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}
bool cmp(dot p1, dot p2){ // 排序函数,按极角大小排序 
	double tmp = cp(p[0], p1, p[0], p2);
    if(tmp > 0) 
		return 1;
    if(tmp == 0 && dis(p[0], p1) < dis(p[0], p2)) 
		return 1;
    return 0;
}
int main(){
	scanf("%d", &n);
	double tmp;
	for(int i = 0; i < n; i++){
		scanf("%lf%lf", &p[i].x, &p[i].y);
		if(i > 0 && (p[i].y < p[0].y || (p[i].y == p[0].y && p[i].x < p[0].x))){ // p[0].y保证最小 
			tmp = p[i].x, p[i].x = p[0].x, p[0].x = tmp;
			tmp = p[i].y, p[i].y = p[0].y, p[0].y = tmp;
		}
	}
	sort(p + 1, p + n, cmp); //从第二项开始排
	s[0] = p[0]; //p[0]一定在凸包中
	int cnt = 0;
	for(int i = 1; i < n; i++){
		// 判断此时栈顶的点是否需要剔除,这步结合上图可理解的更透彻 
		while(cnt > 0 && cp(s[cnt - 1], s[cnt], s[cnt], p[i]) <= 0) 
			cnt -= 1;
		cnt += 1;
		s[cnt] = p[i]; //p[i]入栈 
	} 
	
	s[cnt + 1] = p[0]; //需要求周长, 第一个点再入栈
	double C = 0;
	for(int i = 0; i <= cnt; i++)
		C += dis(s[i], s[i + 1]);
	printf("%.2lf", C);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值