前言

平面点集的凸包算法一文介绍了如何计算平面点集或者任意多边形的凸包。对于随机的平面点集,Graham scan和Andraw's 单调链算法已经是最快的算法了。但是对于没有自相交的封闭的简单多边形,存在线性复杂度的算法。下面介绍这一优雅高效的算法。

一般的2D凸包算法,首先将点进行排序(时间复杂度),然后利用栈操作在O(n)的时间复杂度内计算凸包。初始的排序决定了最终的时间复杂度。但是本文介绍的算法使用一个双端队列来进行操作,避免了排序。由于限定了多边形的简单性(平面单连通域),可以证明队列中的点构成凸包。该算法是由Melkman在1987年提出的。

一些多边形的特征算法可以通过其凸包来高效地求解,其凸包的解就是原来多边形的解。因此,对于简单多边形有一个快速凸包算法的话,可以加速相应算法的计算。例如多边形的直径、切线等算法。

背景

早在1972年,Sklansky就提出了一个O(n)时间复杂度的算法,并给出了实现。不幸的是,6年后Bykat证明他的算法是错误的。基于Sklansky的想法,Graham & Yao在1983年修正了这个算法,给出了一个使用栈操作的正确版本,但是算法的实现十分复杂。

最终,Melkman在1987年给出了一个简洁漂亮的O(n)算法:

  1. 适用于简单多段线(不自交);
  2. 不需要任何预处理,直接按顶点顺序处理;
  3. 使用双端队列存储凸包结果;
  4. 算法的逻辑非常简单。

Melkman算法似乎不太可能被超越了。

简单多边形凸包算法

Melkman's Algorithm

Melkman, 1987年设计了一种巧妙的方法来实现计算简单多段线的凸包,下面将对其进行详细描述。

Melkman Algorithm的策略非常直接,按原始顺序依次处理多段线上的每个点。假定输入多段线为S={P0,P1,...,Pn}。在每一步,算法将当前为止处理过的所有顶点形成的凸包存储在一个双端队列中。

接下来,考虑下一个顶点Pk。Pk有两种情况:(1)Pk在当前凸包内;(2)Pk在当前凸包外,并且形成一个新的凸包顶点。在case (2)中,原来凸包中的点可能变为在新凸包的内部,需要被丢弃,然后再将Pk加入队列。

首先给双端队列两个标签:bot和top,在这中间的是当前凸包结果。在队列两端,都可以增加或删除元素。在顶部(top之上),我们称为push / pop;在底部(bot之下),我们将增删元素的操作称为insert / delete。不妨将队列记为是原多段线中的点。当就形成了一个多边形。在Melkman算法中,处理顶点Pk后,满足:

  1. 是多段线的逆时针方向的凸包;
  2. ,是最近添加到中的点。

对于case(2),我们需要改变,更新队列。在将Pk添加到队列两端之前,需要先将在新凸包内部的点删除。在队列的首尾,通过测试Pk是否在顶部的边的左侧,就可以判断此时top\bot处的点是否需要删除。继续这个检查,直到Pk在队列两端的边的左侧。最后,我们将Pk添加到队列两端。过程如下图:

根据上述过程,很容易分析算法的时间复杂度。每个顶点最多添加到队列中两次(top和bot各一次),队列中的点最多被移除一次,每添加/移除一个顶点,最多需要一次常数量级的isLeft判断。Melkman algorithm最多需要3n次isLeft测试和3n次队列操作。最佳性能是,2n次测试和4次队列操作(当最初的3个点构成最终的凸包结果时)。

因此,Melkman算法非常高效,时间复杂度和空间复杂度都是O(n).

算法伪代码如下:

Input: 有n个顶点的简单多段线P[i]

将初始3个点加入队列 D[] ,使得:
    a) P[2] 在 D[]的top和bot处
    b) 在D[]中,P0、P1、P2形成一个逆时针的三角形

依次处理i=2之后的每一个点,对于P[[i],检查P[i]是否在D的内部:

if  P[i]  在D[bot]D[bot+1] 和 D[top-1]D[top]的左侧 then

跳过P[i],接着处理下一个点;

while P[i] is right of D[bot]D[bot+1] do

Delete D[bot] from the bottom of D[];

Insert P[i] at the bottom of D[];

while P[i] is right of D[top-1]D[top] do

Pop D[top] from the top of D[];

Push P[i] onto the top of D[].

Output: D[]就是最终的凸包结果。

C++实现

// Assume that a class is already given for the object:
//    Point with coordinates {float x, y;}
//===================================================================// isLeft(): test if a point is Left|On|Right of an infinite line.
//    Input:  three points P0, P1, and P2
//    Return: >0 for P2 left of the line through P0 and P1
//            =0 for P2 on the line
//            <0 for P2 right of the line
//    See: Algorithm 1 on Area of Triangles
inline float isLeft( Point P0, Point P1, Point P2 )
{return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y);
}// simpleHull_2D(): Melkman's 2D simple polyline O(n) convex hull algorithm
//    Input:  P[] = array of 2D vertex points for a simple polyline
//            n   = the number of points in V[]
//    Output: H[] = output convex hull array of vertices (max is n)
//    Return: h   = the number of points in H[]
int simpleHull_2D( Point* P, int n, Point* H )
{// initialize a deque D[] from bottom to top so that the// 1st three vertices of P[] are a ccw trianglePoint* D = new Point[2*n+1];int bot = n-2, top = bot+3;    // initial bottom and top deque indicesD[bot] = D[top] = P[2];        // 3rd vertex is at both bot and topif (isLeft(P[0], P[1], P[2]) > 0) {D[bot+1] = P[0];D[bot+2] = P[1];           // ccw vertices are: 2,0,1,2}else {D[bot+1] = P[1];D[bot+2] = P[0];           // ccw vertices are: 2,1,0,2}// compute the hull on the deque D[]for (int i=3; i < n; i++) {   // process the rest of vertices// test if next vertex is inside the deque hullif ((isLeft(D[bot], D[bot+1], P[i]) > 0) &&(isLeft(D[top-1], D[top], P[i]) > 0) )continue;         // skip an interior vertex// incrementally add an exterior vertex to the deque hull// get the rightmost tangent at the deque botwhile (isLeft(D[bot], D[bot+1], P[i]) <= 0)++bot;                 // remove bot of dequeD[--bot] = P[i];           // insert P[i] at bot of deque// get the leftmost tangent at the deque topwhile (isLeft(D[top-1], D[top], P[i]) <= 0)--top;                 // pop top of dequeD[++top] = P[i];           // push P[i] onto top of deque}// transcribe deque D[] to the output hull array H[]int h;        // hull vertex counterfor (h=0; h <= (top-bot); h++)H[h] = D[bot + h];delete D;return h-1;
}

多边形快速凸包算法(Melkman‘s Algorithm)相关推荐

  1. 经典文献翻译:合成孔径雷达快速反投影算法(Fast Backprojection Algorithm for Synthetic Aperture Radar)

    排版不方便,图片不好上传,Word版翻译及原文下载地址://download.csdn.net/download/u010112513/12576783 Ali F. Yegulalp 翻译:Ivan ...

  2. 求多边形凸包(线性算法)--陈氏凸包算法--

    http://blog.sina.com.cn/s/blog_616e189f0100qc0u.html 陈氏凸包算法-算法参考:Computing the convex hull of a simp ...

  3. matlab 凸包质心算法,求多边形凸包(线性算法)--陈氏凸包算法--Computing the convex hull of a simple polygon(源码)...

    陈氏凸包算法-算法参考:Computing the convex hull of a simple polygon 作者:Chern-Lin Chen 陈氏算法提供了一个线性效率求凸包的算法,本文使用 ...

  4. CGAL笔记之凸包算法—3D凸包

    CGAL笔记之凸包算法-3D凸包 1 介绍 2 静态凸壳结构 2.1 特性类 2.1.1 示例 2.1.2 低维结果示例 2.2 极值点 2.3 半空间交集 2.3.1 例子 2.4 凸性检查 3 动 ...

  5. java 地理围栏实现_使用Path2D和凸包算法实现地理围栏服务

    前言 地理围栏(Geo-fencing)是LBS的一种新应用,就是用一个虚拟的栅栏围出一个虚拟地理边界.在物流配送行业应用比较广,划分每个配送网点或者商家配送的范围,提高配送员的配送效率和服务的范围. ...

  6. python 快速行进 算法 图像修补

    图像修复 很多时候遇到受损的图片我们需要利用机器视觉的手段对其进行修复,opencv中提供了inpaint函数实现了这一功能. 1.先来看一个例子 首先读入图片: import numpy as np ...

  7. 复杂多边形光栅化算法

    虽然已经一年多没有维护gbox这个图形库项目了,最近确实时间不够用... 今年的重点是把xmake彻底正好,至少在架构和大功能(包依赖管理)上,要完全落实下来,后期就是零散的维护和插件功能扩展了.. ...

  8. Graham Scan凸包算法

    获得凸包的算法可以算是计算几何中最基础的算法之一了.寻找凸包的算法有很多种,Graham Scan算法是一种十分简单高效的二维凸包算法,能够在O(nlogn)的时间内找到凸包. 首先介绍一下二维向量的 ...

  9. 汉语词典快速查询算法研究

    原贴:http://www.nlp.org.cn/docs/docredirect.php?doc_id=1118 汉语词典快速查询算法研究 李江波 周强 陈祖舜 (清华大学智能技术与系统国家重点实验 ...

最新文章

  1. android面试题总结加强
  2. 本科计算机论文摘要怎么写,★本科计算机论文摘要范文本科计算机论文摘要写...
  3. Java技巧:用一个循环语句输出九九乘法表!
  4. 云原生势不可挡,华为云GaussDB加速企业数字化转型
  5. 去哪儿网查不到历史订单_去哪儿网 数据清洗
  6. 敏捷开发日常跟进系列之三:故事板,看板
  7. char *s 与 char s[ ]的区别
  8. 条码打印一 - Zebra斑马打印机三种打印方式的利弊
  9. 光耦驱动单向可控硅_双向晶闸管的触发用的光耦驱动mos桥,光耦
  10. Mac 下读写NTFS文件
  11. 设计一个O(n2)时间的算法,找出由n个数组成的序列的最长单调递增子序列。
  12. PC与S7 1200PLC通讯
  13. 实验九 使用异步方式实现文件读\写
  14. iMazing2021mac win最先进的苹果iPhone和iPad管理软件
  15. 深度学习与OpenCV DNN模块:权威指南
  16. 浅谈CMPP3协议架构实现
  17. rf 遍历列表_RF的变量list在For循环的用法,试错中学习
  18. 空间平面的旋转与位移
  19. 湖南大学计算机学院院领导,罗娟-湖大信息科学与工程学院
  20. 【Duilib基础控件】滚动条CScrollBarUI设置

热门文章

  1. Black Hat Europe 2021议题解读:Wi-Fi Mesh中的安全攻击面
  2. Unity3D_椭圆算法
  3. visual studio community 2019安装
  4. Spark RDD 简述
  5. [量化-007]为什么股市会存在
  6. 浅谈:为什么vue和react都选择了Hooks?
  7. elecV2P的安装及使用
  8. 计算机导论期末考试知识点,计算机导论期末复习(知识点).doc
  9. oracle 标示符太长,Oracle PLS-00114: 标识符 ' ' 太长
  10. POJ1087A Plug for UNIX(会议室的插座)——最大流