计算机视觉——凸包计算

现在有一大堆点,然后你要找出一个可以围住这些点且面积最小的凸多边形,这个凸多边形称为凸包。

显而易见,如果要面积最小,那凸包的顶点势必得是这一大堆点的几个点,你也可以想成是用一条橡皮筋把这些点圈起来。

先把各个点按坐标从小到大排序,坐标相同则再按坐标从小到大排序,排序之后的点顺序会是由左至右、由下至上,这样一来我们就可以按这个顺序遍历这些点,这种往固定方向扫描的方式,称为扫描线。

先讨论一件事情:有一个凸多边形,它的顶点已经按逆时针顺序排好了,依次是 p 1 , p 2 , . . . , p n p_1, p_2, ..., p_n p1,p2,...,pn ,那么 p i p_i pi p j p_j pj p k p_k pk 的关系会是什么(令 i < j < k i < j < k i<j<k既然是凸多边形,那么它的边应该是要一直往同个方向转弯的,而如果将边逆时针排序,这个边的斜率也应该是一直往逆时针方向转弯,显然点也会是这样,因此:

再来,我们把凸包分成上下两部分:上凸包和下凸包,以极左和极右点分割,如果极左点或极右点有两个(最多只会有两个),上面那个属于上凸包,下面那个属于下凸包,否则极左点必属于下凸包,极右点必属于上凸包。

显然,上或下凸包中不会有 x 坐标相同的顶点,因此在上或下凸包中,每个顶点都能分别出左右的关系的,并且你会发现,如果把点按逆时针排序,在下凸包中的点也是由左而右排序的、在上凸包的点也是由右而左排序的。

这样一来左右关系就有用了,先用由左而右的扫描线把下凸包做出来,再用由右而左的扫描线把上凸包做出来,就可以得到整个凸包。

整个流程可以用一个栈来实现,在处理一个点的时候,我们尝试把它加进凸包里,此时这个点是 ( p ) ,栈顶的点是 ( top ) ,栈顶再往下一个点是 ( second ) ,把这些点代入刚刚的式子,符合条件或者栈大小小于 2 时就停止,否则就把栈顶 pop,然后继续重复,结束后就把目前处理中的点放入栈顶。

在做下凸包的时候,先从最左边且最下面的点开始做上述动作,做到最后,栈顶的点应该是最右边且最上面的点,把它 pop 掉,因为它应该属于上凸包;做上凸包的时候,从最右边且最上面的点开始做,最后栈顶会是最左且最下的点,把它 pop 掉后,这两个接起来就是完整的凸包。

因为要用到栈顶往下一个点,所以栈用 vector 来实现。

#include <bits/stdc++.h>#define mp(a, b) make_pair(a, b)
#define pb(a) push_back(a)
#define F first
#define S secondusing namespace std;template<typename T>
pair<T, T> operator-(pair<T, T> a, pair<T, T> b){return mp(a.F - b.F, a.S - b.S);
}template<typename T>
T cross(pair<T, T> a, pair<T, T> b){return a.F * b.S - a.S * b.F;
}template<typename T>
vector<pair<T, T>> getConvexHull(vector<pair<T, T>>& pnts){int n = pnts.size();sort(pnts.begin(), pnts.end());vector<pair<T, T>> hull;for(int i = 0; i < 2; i++){int t = hull.size();for(auto& pnt : pnts){while(hull.size() - t >= 2 && cross(hull.back() - hull[hull.size() - 2], pnt - hull[hull.size() - 2]) <= 0)hull.pop_back();hull.pb(pnt);}hull.pop_back();reverse(pnts.begin(), pnts.end());}return hull;
}

旋转卡尺

用旋转两条平行线、夹住一堆点,看在线上的点是哪些,就叫旋转卡尺。

旋转卡尺1

旋转线、夹点感觉很麻烦,是不是要用什么角度的东西啊?其实不用,先来分析一下问题,用两条平行线夹一堆点,那么平行线只会碰到凸包上的点而已,所以不在凸包上的点都可以先忽略:

旋转卡尺2

过一个点的直线有很多条,但是过一个线段的直线只有一条,所以先枚举线段,再去找和它平行的直线应该会夹到哪个点,这样问题就简单多了。要找平行线会碰到哪个点,显然离线段最远的点就是了。

不过算距离是另一个问题,听起来也很麻烦,但其实很简单。一个点距离一条直线的距离,等同于过该点在直线上作垂线段的长,而一开始选定的线段作为底、垂线段长作为高,那么就可以得一个平行四边形面积了,且底的长是固定的,只要枚举最远点,就等同于枚举高,而得出面积最大的,就是我们要求的最远点。

旋转卡尺3

上图中,选定两个红色点所连成的线段为底,然后枚举各个顶点取高,得出蓝色垂线是最长的,因此蓝色点就是距离红色线段最远的点。

这就是旋转卡尺的基础应用——最远点对,找到距离每一线段最远的点,再取该点与线段两端点的距离取最大值,这样就可以得出所有点中最远的点对为何。

硬要这么做的方式,时间复杂度是 O ( n 3 ) O(n^3) O(n3) n n n 是凸包上点的数量(不计盖凸包的复杂度),枚举线段是 O ( n 2 ) O(n^2) O(n2) ,再枚举一个点要再乘上 n n n

这不够快,我们需要更有效率的方式。

仔细观察一下,点和线段的距离有一个规律——先渐大,到一个最大值,再渐小:

旋转卡尺4

会发现它会呈现一个单峰函数,也就是一个先递增、再递减的函数,这样我们就可以三分搜找到最高点了,这样三分搜一次的复杂度是 O ( n ) O(n) O(n),再乘上点的数量,就是 O ( n 2 ) O(n^2) O(n2)

这样子还是不够快,前面提到旋转卡尺是「旋转两条平行线」,刚才的动作都是旋转其中一条,再去搜寻另一条,那我们可不可以在旋转其中一条的同时,把另一条一起旋转?答案是:可以。

(以下的转都是指往同一个方向转)
先找到距离第一条边最远的点,过前者的线称为第一条平行线,过后者的称为第二条平行线,接下来我们转动第一条平行线,也就是把它转到第二条线段上,而第二条平行线不要动,会发现,第一条平行线离第二条平行线那个点近了一些,接着再转第二条平行线,也就是把它转到下一个点上,那么距离会变远。

也就是,可以在不重新来过的情況下,找到单峰函数的最高点,会发现这样就是把两条平行线绕一圈,因此这样的复杂度是 O ( n ) O(n) O(n)

原文地址:https://cp.wiwiho.me/convex-hull/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395631.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中的 `break` 语句:掌握循环控制的艺术

Python中的 break 语句&#xff1a;掌握循环控制的艺术 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕…

【大模型系列篇】论文解读:Transformer - Attention Is All You Need (2017)

Attention Is All You Need (Transformer) 是当今大模型初学者必读的一篇论文&#xff0c;已经有不少业内大佬都翻译解读过这篇论文&#xff0c;此处仅作为自己学习的记录。该论文是由谷歌机器翻译团队于2017年发表在NIPS &#xff0c;提出了一个只基于attention的结构来处理序…

【iOS】OC关键字总结及底层原理(上)

目录 线程安全相关的关键字atomic&nonatomic 作用域相关的关键字static、extern、const&auto 读写权限相关和指定方法名的关键字内存管理相关的关键字&#xff08;或方法&#xff09;1. 引用计数的存储SideTableretain方法源码分析release方法源码分析dealloc方法源码分…

嵌入式初学-C语言-十九

指针的引入 为函数修改实参提供支持为动态内存管理提供支持为动态数据及结构提供支持为内存访问提供另一种途径 指针的概述 内存地址&#xff1a; 系统为了内存管理的方便将内存划分为一个个内存单元&#xff08;一个内存单元占一个字节&#xff09;&#xff0c;并为每一个…

用Vue和Axios将数据库数据显示在前端页面

在本次实例中Vue只用在了前端部分&#xff0c;Axios用于向后端请求数据&#xff0c;我们这里要用到Ajax技术来访问后端数据。 HTML&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name&quo…

全新博客X主题/简约WordPress主题模板/主题巴巴/免授权版源码+自适应设计

源码简介&#xff1a; 博客X这款超酷的Wordpress主题&#xff0c;是主题巴巴团队打造的设计杰作。想象一下&#xff0c;你的博客首页能展示那些炫酷的幻灯片置顶文章、还有各种精心策划的专题列表&#xff0c;这些内容模块的设计简直吸睛了&#xff0c;能让来访的用户眼前一亮…

数据结构和算法|递归算法那些事(递归算法的时间复杂度、尾递归优化、斐波那契数列)

对于文章的第一部分&#xff0c;递归算法的时间复杂度&#xff0c;来自于代码随想录文章:通过一道面试题目&#xff0c;讲一讲递归算法的时间复杂度&#xff01; 对于第二节尾递归优化来自于B站&#xff1a;尾递归优化&#xff1a;你的递归调用是如何被优化的&#xff1f; 文章…

XML(可扩展标记语言)

QDomDocument doc;QDomElement ss doc.createElement("root");//创建标签 //ss标签添加到文档对象doc.appendChild(ss);//doc.save()auto hero doc.createElement("hero");ss.appendChild(hero);hero.setAttribute("id",10086);//为hero添加属…

MySQL——数据表的基本操作(一)创建数据表

数据库创建成功后,就需要创建数据表。所谓创建数据表指的是在已存在的数据库中建立新表。需要注意的是&#xff0c;在操作数据表之前&#xff0c;应该使用 “ USE 数据库名 ” 指定操作是在哪个数据库中进行&#xff0c;否则会抛出 “ No database selected ” 错误。创建数据表…

Tomcat 使用和配置文件(详解)

一.tomcat 介绍 1. tomcat 概述 自从JSP发布之后&#xff0c;推出了各式各样的JSP引擎。Apache Group在完成GNUJSP1.0的开发以后&#xff0c;开始考虑在SUN的JSWDK基础上开发一个可以直接提供Web服务的JSP服务器&#xff0c;当然同时也支持 Servlet&#xff0c;这样Tomcat就诞…

[自学记录09*]关于模糊效果降采样优化性能的小实验

一、降采样在模糊中的优化 这两天接手了几个高度定制化的模糊&#xff0c;包括不限于放射和旋转状的径向模糊&#xff0c;移轴模糊&#xff0c;景深的散景模糊等等&#xff0c;这些效果在游戏中非常常见。 其实模糊的原理都差不多&#xff0c;无非就是对UV偏移后重新采样再求…

《Python爬虫逆向实战》绕过debugger的方法汇总

禁用断点 打开控制台&#xff0c;点击右边的禁用断点按钮。 点击之后再刷新下&#xff0c;就会发现debugger失效了。 注&#xff1a;这种方法有个 弊端&#xff0c;就是我们在代码中下的断点也都将失效。 Add script to ignore list 在代码文件中任意位置右键&#xff0c;然…

51单片机—串口

一、 串口基本认知 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&a…

【C++ 面试 - 基础题】每日 3 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

【网络安全】玲珑安全第四期

鉴于玲珑安全漏洞挖掘前三期课程取得的优异成绩和获得的强烈反响,我们决定启动玲珑安全第四期漏洞挖掘培训计划。 文章目录 往期学员收获基础学员报喜(部分)课程反馈第四期课程课程内容免费课程往期学员收获 第一期课程总结及学员收获:->点我查看第一期学员收获<- …

性能测试工具LoadRunner

前言&#x1f440;~ 上一章我们介绍了性能测试的一些基本概念&#xff0c;重要的是性能测试的各项指标&#xff0c;今天我们使用性能测试工具LoadRunner简单的完成一次性能测试 性能测试Load Runner LoadRunner是什么&#xff1f; LoadRunner安装 LoadRunner脚本录制 1.录…

算法板子:质数——判定质数、分解质因数、筛质数

目录 一、判定质数 1. 代码 二、分解质因数 1. 质因数的概念 2. 代码 三、筛质数——获取1~n中所有质数的个数 1. 合数的概念 2. 代码 一、判定质数 1. 代码 #include <iostream> using namespace std;bool is_prime(int x) {// 1不是质数, 需要特判if (x 1) …

QT键盘和鼠标事件

这些事件都在QWidget 中的保护成员方法中 都是虚函数在头文件中声明了 需要类外重现实现 如果头文件中声明 类外无实现就会报错 void Widget::keyPressEvent(QKeyEvent *event) {switch (event->key()) {//获取按键case Qt::Key_W://按键wqDebug()<<"按下w"…

开源免费前端地图开发组件xdh-map

xdh-map是一个基于Openlayers的地图应用Vue组件&#xff0c;具有多方面的功能和特点。以下是对xdh-map的详细介绍&#xff1a; 一、功能与特性 内置多种地图瓦片&#xff1a;xdh-map内置了百度、高德、天地图等地图瓦片&#xff0c;使得开发者可以方便地在应用中集成多种地图…

【Material-UI】Checkbox 组件中的 Label Placement 设置详解

文章目录 一、Checkbox 组件简介1. 组件概述2. labelPlacement 属性 二、labelPlacement 属性的使用方法三、各标签位置的效果与应用场景1. Top&#xff08;顶部&#xff09;2. Start&#xff08;左侧&#xff09;3. Bottom&#xff08;底部&#xff09;4. End&#xff08;右侧…