Graham算法

守給你的承諾、 提交于 2020-01-15 07:24:19

前言:本菜鸡的第一篇算几,记录一下我有多菜

Graham算法

一种凸包算法,扫描部分的时间复杂度为O(n)\mathcal{O}(n),总的时间复杂度O(nlogn)\mathcal{O}(nlogn)

原理:从点集中先找出一个最左下方的点,易证这个点肯定在凸包上,然后以这点为极点,将所有点根据与这点的极角进行排序,并且同时使用一个栈结构维护凸包上的点。按照极角序依次将当前点与栈顶的两个点作拐向判断:若右拐,则将当前点加入栈中,否则将栈顶点弹出。当点集遍历完之后,还在栈中的点就是凸包上的点。

算法步骤

  1. 对所有点进行排序,选择xx坐标最小的点作为极点,即找到第一个一定在凸包上的点
bool cmp1(const Pnt &a,const Pnt &b){
	return a.x!=b.x?a.x<b.x:a.y<b.y;
}
  1. 将其余所有点按照极角排序,在极角相同的情况下比较与极点的距离,按极角序依次处理每一个点
inline bool cmp2(const Pnt &st,const Pnt &ed){//按极角排序 
	int del=p[st]*p[ed];
	if(del!=0)return del>0;
	return (getdis(p[st]-p[1])-getdis(p[ed]-p[1]))<eps;
}
  1. 用一个栈SS储存凸包上的点,先将按极角和极点排序最小的两个点入栈(其实这个地方可以用重载运算符来写,但是不知道为什么我语法一直报错来着qwq一定是我太菜辽
db getdis(const Pnt &a,const Pnt &b){return (db)sqrt((db)(a.x-b.x)*(a.x-b.x)*1.0+(db)1.0*(a.y-b.y)*(a.y-b.y));}
db cj(Pnt a1,Pnt a2,Pnt b1,Pnt b2){return (db)(a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);}
inline bool cmp2(Pnt st,Pnt ed){
	db tmp=cj(p[1],st,p[1],ed);
	if(tmp>0)return 1;
	if(tmp==0&&getdis(p[0],st)<(getdis(p[0],ed)))return 1;
	return 0;
}
  1. 按序扫描每个点,检查栈顶的前两个元素与这个点构成的折线段是否向左偏(叉积零)(即构成的有向面积为负或共线,共线可以根据和极点距离来算,不过前面已经做过处理,这里就不用了)
  2. 如果满足,则弹出栈顶元素,并返回step4step4再次检查,直到不满足,将该点入栈,并对其余点不断执行此操作
	for(int i=2;i<=n;i++){
		while(tot>1&&cj(sta[tot-1],sta[tot],sta[tot],p[i])<=0)tot--;
		tot++;
		sta[tot]=p[i];	
	}
  1. 最终栈中元素为凸包的顶点序列
一道板子题

P2742 【模板】二维凸包 / [USACO5.1]圈奶牛Fencing the Cows

#include <bits/stdc++.h>
#define db double
#define eps 1e-8
using namespace std;
const int N=(1e5)+50;
int n;
double ans,mid;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c&15);c=getchar();}
	return cnt*f;
}
struct Pnt{double x,y;}p[N],sta[N];
db getdis(const Pnt &a,const Pnt &b){return (db)sqrt((db)(a.x-b.x)*(a.x-b.x)*1.0+(db)1.0*(a.y-b.y)*(a.y-b.y));}
db cj(Pnt a1,Pnt a2,Pnt b1,Pnt b2){return (db)(a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);}
inline bool cmp2(Pnt st,Pnt ed){
	db tmp=cj(p[1],st,p[1],ed);
	if(tmp>0)return 1;
	if(tmp==0&&getdis(p[0],st)<(getdis(p[0],ed)))return 1;
	return 0;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		scanf("%lf%lf",&p[i].x,&p[i].y);
		if(i!=1&&p[1].y>p[i].y){
			swap(p[i],p[1]);
		}
	}
	sort(p+2,p+1+n,cmp2);
	sta[1]=p[1];
	int tot=1;
	for(int i=2;i<=n;i++){
		while(tot>1&&cj(sta[tot-1],sta[tot],sta[tot],p[i])<=0)tot--;
		tot++;
		sta[tot]=p[i];	
	}
	sta[tot+1]=p[1];
	//因为这里是要构成一个圈,所以最后一个点一定要加入栈!!!
	for(int i=1;i<=tot;i++) ans+=getdis(sta[i],sta[i+1]);
	printf("%.2lf",ans);
	return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!