凸包算法

先理解下凸包

说凸包首先要说凸性的定义,简单点说就是平面邻域中任意两点所在的线段上的点都在该邻域中,则该邻域具有凸性。简单推敲一下,就可以发现如果邻域中存在一阶导数不连续的点一定无法被某点集线性表示出来。再往下的内容属于数学分析了,对我们的算法设计帮助不大,暂时先不管。

一般的计算几何问题都是处理的离散点集形成的平面域,所以我们感兴趣的是怎样找一个包含这个点集的面积最小的凸多边形,这就是凸包。作为常识也应该知道凸包上的顶点必然是该点集的子集,所以根据此性质我们就可以设计高效算法。

下面将介绍三种求平面点集凸包的方法,要特别注意求多边形的凸包和平面点集的凸包的联系,这样才有助于深入学习。

Gift wrapping method:这招我想还是不说了,因为我觉得应该不会有人会用,除了比较好理解外没什么好处。

Graham-Scan:这应该是最早的O(nlgn)算法了,实现也比较简单,其基本思想是维护一个凸曲线,因此它要求算法开始时必须至少知道一个必然在凸包上的点作为其始点(还好这比较简单)。它有个缺点就是直接用它去求一个给定多边形的凸包可能会导致错误(算法艺术上给了样例),因此算法开始前必须将点有序化。

Melkman:迄今为止最好的凸包算法了,我强烈推荐的算法。它的基本操作和Graham-Scan一样,只不过它在任意时候都求得当前已考察点所形成的凸包,所以它有一个无可比拟的优势就是它是一个在线算法(要想往点集中增加一个点不必重新计算)。对于给定一个多边形,它可以直接求其凸包而不用先有序化。而它还有个最大的好处是实现非常简单,所以特别适合比赛中使用。

虽说Melkman好,但是Graham_scan却是常用的

预备篇 点的排序与左转判定

点的排序

  找给定点集的凸包,通常需要一些预处理过程,点的排序就是其中之一。下面给出一种将点按照一定规则排序的方法,这个预处理过程在很多凸包寻找算法中都扮演重要角色。

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />

1.找一个必在凸包上的点(这很容易^_^,通常取横坐标或纵坐标最小的点),记为P0,

2.连结P0与其他点,分别计算这些线段与“竖直向下方向”的夹角,按照夹角由小到达的顺序将各线段的另一端(一端是P0)标号为P1、P2、P3……

左转判定

 

这是经典的计算几何学问题,判断向量p1=(x1,y1)到p2=(x2,y2)是否做左转,只需要判断x1*y2-x2*y1的正负,如果结果为正,则从p1到p2做左转。也就是向量的叉积。

Graham算法是这样的

1.将各点排序(请参看基础篇),为保证形成圈,把P0在次放在点表的尾部;

2.准备堆栈:建立堆栈S,栈指针设为t,将0、1、2三个点压入堆栈S;

3.对于下一个点i

   只要S[t-1]、S[t]、i不做左转

     就反复退栈;

   将i压入堆栈S

4.堆栈中的点即为所求凸包;

  其核心用C语言表示,仅仅是下面一段:

t=-1;

s[++t]=0; s[++t]=1; s[++t]=2;

for (i=3;i<n;i++)

{

while (!left(s[t-1],s[t],i))

t--;

s[++t]=i;

}

比较完整的代码

int top;//凸包的顶点个数
struct point
{
 int x,y;
}p[maxn],stack[maxn];

int max(int a,int b)
{
 return a>b?a:b;
}

int dis(point p1,point p2)//两点的距离的平方
{
 return (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);
}

//叉积,结果小于表示向量p0p1的极角大于p0p2的极角,等于则两向量共线
int  multi(point p1, point p2, point p0)
{

return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}

int cmp(point a,point b)
{
 if(multi(a,b,p[0])>0)
  return 1;
 if(multi(a,b,p[0])==0&&dis(a,p[0])<dis(b,p[0]))
  return 1;
 return 0;
}

//Graham_scan的精华
void Graham_scan(point p[],point stack[],int n)
{
 
 int i,j,k=0;
 top=2;
 point temp;
 //寻找最下且偏左的点
 for(i=1;i<n;i++)
  if(p[i].y<p[k].y||((p[i].y==p[k].y)&&(p[i].x<p[k].x)))
   k=i;
 //将该点指定为p【0】;
 temp=p[0];
 p[0]=p[k];
 p[k]=temp;
 //按极角从小到大,距离偏短进行排序

//以上是本质,如果按以上的写,可能会超时,还是用下面的sort
 sort(p+1,p+n,cmp);
 //核心
 stack[0]=p[0],stack[1]=p[1],stack[2]=p[2];
 for(i=3;i<n;i++)
 {
  while(top>1&&multi(p[i],stack[top],stack[top-1])>=0)
   top--;
  stack[++top]=p[i];
 }
}

凸包算法

时间: 2024-11-05 18:34:41

凸包算法的相关文章

计算几何-凸包算法 Python实现与Matlab动画演示

凸包算法是计算几何中的最经典问题之一了.给定一个点集,计算其凸包.凸包是什么就不罗嗦了 本文给出了<计算几何——算法与应用>中一书所列凸包算法的Python实现和Matlab实现,并给出了一个Matlab动画演示程序. 啊,实现谁都会实现啦╮(╯▽╰)╭,但是演示就不一定那么好做了. 算法CONVEXHULL(P)  输入:平面点集P  输出:由CH(P)的所有顶点沿顺时针方向组成的一个列表 1.   根据x-坐标,对所有点进行排序,得到序列p1, …, pn 2.   在Lupper中加入p

Graham Scan凸包算法

获得凸包的算法可以算是计算几何中最基础的算法之一了.寻找凸包的算法有很多种,Graham Scan算法是一种十分简单高效的二维凸包算法,能够在O(nlogn)的时间内找到凸包. 首先介绍一下二维向量的叉积(这里和真正的叉积还是不同的):对于二维向量a=(x1,y2)和b=(x2,y2),a×b定义为x1*y2-y1*x2.而它的几何意义就是|a||b|sin<a,b>.如果a与b夹角小于180度(逆时针),那么这个值就是正值,大于180度就是负值.需要注意的是,左乘和右乘是不同的.如图所示:

openlayer的凸包算法实现

最近在要实现一个openlayer的凸多边形,也遇到了不小的坑,就记录一下 1.具体的需求: 通过在界面点击,获取点击是的坐标点,来绘制一个凸多边形. 2.思考过程: 1)首先,我们得先获取点击事件发生时,触发的点的坐标 map.events.register('click', map, function (e) { var pixel = new OpenLayers.Pixel(e.xy.x,e.xy.y); var lonlat = map.getLonLatFromPixel(pixel

线段余弦角+凸包算法

/// /// 根据余弦定理求两个线段夹角 /// /// 端点 /// start点 /// end点 /// double Angle(PointF o, PointF s, PointF e) { double cosfi = 0, fi = 0, norm = 0; double dsx = s.X - o.X; double dsy = s.Y - o.Y; double dex = e.X - o.X; double dey = e.Y - o.Y; cosfi = dsx * de

平面上圆的凸包算法

平面上圆的凸包算法 我们之前探讨过这个有趣的问题: 平面上有若干圆,求包含这些圆的所有凸集的交. 根据之前讨论的结果,直接按圆心排序做扫描线的方法是错误的.我们需要考虑圆上的每一个点是否可以作为凸包上的一部分. 然而圆上的点的数目是无限多的.我们需要借助离散化的思想:因为我们发现凸包一定是由若干圆弧和线段构成的.而其中线段一定是切线段.由此,我们只需要将每两两圆的切点取出来求一个凸包.显然,在圆凸包上出现的切线段一定会在切点凸包中出现:而切点凸包中其余的线段则一定是弧弦. 但是,这个算法需要枚举

凸包算法的应用——数一数图形中共有多少三角形

一.问题引入 网络上经常会遇到判断图形个数的题目,如下例: 如果我们要把图中所有三角形一个一个选出来,在已知每个交点的前提下,该如何用代码判断我们选的图形是否是三角形呢.如下图,如何把图3筛选出来呢? 这里需要用到两步: 1.得到所选图形(阴影部分)所包含的所有小图形的顶点集合,求集合的凸包,根据凸包顶点个数判定凸包围成的图形是否是三角形,若顶点个数不为3则不是三角形,如图(1). .2.若凸包围成的图形是三角形,判断凸包的面积与所选图形(所有选中的小图形面积之和)是否相等,若相等则所选图形是三

opengl:凸包算法

准备工作 判断点在有向线段的左侧 可以通过叉积判断,如下为k在有向线段ab的左侧代码描述: double multiply(Point a, Point b, Point k) { double x1 = b.x-a.x; double y1 = b.y-a.y; double x2 = k.x-a.x; double y2 = k.y-a.y; return x1*y2-x2*y1; } bool toLeft(Point a, Point b, Point k) { return multi

凸包算法-GrahamScan+暴力+分治

RT.求平面上点集的凸包. 1. GrahamScan算法,<算法导论>上的例子,先找到y最小的点O,以O建立极坐标,其它点按极角排序后再从头开始扫描(配合stack实现). 2.BruteForce算法,依赖定理:如果一个点在平面上某三个点组成的三角形内,那么这个点不可能是凸包上的点. 所以暴力的思路是平面上的点每4个进行枚举,并判断是否满足定理,若满足,则删除这个点继续找:一直找到没有满足定理的点为止.. 3.DivideAndConquer思路:有很多种,这里我实现的只是严格意义上最后一

587. Erect the Fence(凸包算法)

问题 给定一群树的坐标点,画个围栏把所有树围起来(凸包). 至少有一棵树,输入和输出没有顺序. Input: [[1,1],[2,2],[2,0],[2,4],[3,3],[4,2]] Output: [[1,1],[2,0],[4,2],[3,3],[2,4]] 思路和代码 1. 暴力法(超时) 对于任意两点连成的一条直线,如果其它所有点都在这条直线的一侧,则这两个点为解集中的两个点. 怎么判断点在直线的同一侧呢? 假设确定直线的两点为p1(x1, y1)和p2(x2, y2),方向从p1到p