当前位置: 首页 > ds >正文

Graham 算法求二维凸包

前言

本篇文章原为笔者七升八数学论文作业,如有纰漏,请各位指出,在此感激不尽!

二维凸包的定义

我们先看一张图:

在这张图片里,一个平面上有数个点,同时还有一个凸多边形将这数个点完全覆盖,那么使这个图形面积最小的凸多边形就称为凸包。更具象化地讲,就是用手撑开一个橡皮筋并用其把一堆钉子围住,然后再松开手使橡皮筋收缩,那么最终橡皮筋形成的图形便是这些钉子的凸包。

二维凸包的求法

本文章主要介绍使用 Graham 算法求解二维凸包。

Graham 算法

我们先介绍几个前置知识;

  • 极角
  • 向量叉积

极角

极角指的是在极坐标中,平面上任意一点到极点的连线与极轴的夹角,如图:

我们设 xxxcos⁡(θ)\cos(\theta)cos(θ)yyysin⁡(θ)\sin(\theta)sin(θ)。则:

∵tan⁡(yx)=θ\because \tan(\frac{y}x) = \thetatan(xy)=θ

∴θ=arctan⁡(yx)\therefore \theta = \arctan(\frac{y}x)θ=arctan(xy)

这个公式在进行按极角大小排序时将会很有用。

向量叉积

向量叉乘是有大小有方向,并且不满足交换律的运算。在求解凸包的过程中,我们并不关心向量的大小,而是关心后者,即其方向。由于向量的运算满足右手法则,所以当两向量的叉积为正时,后一个向量便在前者的左边,在下文称之为左拐;否则,若两向量的叉积为负,那么后一个向量便在前者的右边,在下文称之为右拐。如下图:

通过该运算,我们可以很清楚的判断两向量的位置关系,这在使用 Graham 算法求解凸包中发挥着很大的作用。

Graham 算法求解凸包

下面我将先介绍一下该算法求解凸包的流程:

  1. 首先选取一个 yyy 坐标最小的点作为起始节点,如果有多个点的 yyy 坐标相同,那就选择 xxx 坐标做小的点,此点必为凸包上的点。
  2. 令起始节点为极坐标原点,按平面内其它点的极角从小到大进行排序,如果极角相同,按其到原点的距离从大到小排序。
  3. 按排序后的顺序将原点和第一个点加入到一个栈中。
  4. 依次遍历排序后的点,每遍历到一个点时将栈顶的点和栈顶下面的点连线得到直线 lll,然后判断遍历到的新点与栈顶元素所连成的直线与 lll 为左拐、右拐或者处在同一直线上。如果是右拐或在同一直线上,则执行步骤五;否则执行步骤六。
  5. 栈顶元素不在凸包上,出栈,执行步骤四。
  6. 新点已处于凸包上,入栈。
  7. 如果此时排序后的最后一个点以入栈,那么算法结束,否则执行步骤四。

最后,栈中的所有元素便是凸包上的点了。

正确性证明

我们将通过两个方面证明该算法正确性。

栈中所有点均为凸包顶点
  • 原点为凸包顶点:因为原点 yyy 坐标以及 xxx 坐标均为最小的,所以它必然为凸包顶点。
  • 栈中其它点均为凸包顶点:假设栈中有一点 ppp 不为凸包顶点,那么 ppp 必为凸包边上或内部一点。但因为每次遇到在同一直线上或者在栈顶元素连线的右边的点时都会弹出栈顶元素(即步骤四),这样,便使得凸包内部和边上的点均被弹出。所以凸包边上和内部的点必然不会在算法结束后出现在栈内。
凸包顶点均在栈内

假设凸包顶点 qqq 不在栈内,那么 qqq 必然在算法进行时存在后继点 q1q_1q1 使得 qqqq1q_1q1 所连成的直线与 qqq 和原栈中的元素所连成的直线右拐。但因为在进行极角排序以后,凸包的顶点必然逆时针排列,即凸包的任意连续的三个顶点 pi−1,pi,pi+1p_{i-1}, p_i, p_{i+1}pi1,pi,pi+1 均满足左拐。所以在处理凸包顶点 qqq 与后继节点 q1q_1q1 时必满足左拐,所以 qqq 必然在栈内。

时间复杂度分析

在算法过程中极角排序的时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn),而每个点只会入栈和出栈一次,所以时间复杂度为 O(n)O(n)O(n)。总时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn),效率较高。

代码实现(实现语言为 C++)

对于极角排序,我们可以使用内置的 atan2函数,即计算 arctan⁡\arctanarctan 值。而每次判断栈顶元素是否需要出栈便相当于判断此时栈顶的两个点 AAABBB 和 新点 CCC 是否满足 AB→\overrightarrow{AB}ABBC→\overrightarrow{BC}BC 是否为左拐,这便是判断 AB→×BC→≥0\overrightarrow{AB} \times \overrightarrow{BC} \ge 0AB×BC0,我们可以使用向量叉积来判断。

以下 C++ 求解凸包的代码:

#include<bits/stdc++.h>
#define ll long long
#define inf (1ll << 62)
#define pb push_back
#define mp make_pair
#define PII pair<int , int>
#define fi first
#define se second
using namespace std;
struct Point {long double x , y , a;
};
vector<Point>pos;inline long double dis(Point a , Point b) {//计算两点距离return sqrtl((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}inline long double cross(Point a , Point b) {//计算向量叉积return a.x * b.y - b.x * a.y;
}inline void solve() {int n;cin >> n;for(int i = 0;i < n;i ++) {long double x , y;cin >> x >> y;pos.pb({x , y , 0});}for(int i = 1;i < n;i ++) {//找出 y 坐标且 x 坐标最小的点if(pos[i].y < pos[0].y || (pos[i].y == pos[0].y && pos[i].x < pos[0].x)) {swap(pos[i] , pos[0]);} }for(int i = 1;i < n;i ++) {//计算极角pos[i].a = atan2(pos[i].y - pos[0].y , pos[i].x - pos[0].x);}sort(pos.begin() + 1 , pos.end() , [](Point &x , Point &y) {//极角排序if(x.a == y.a) return dis(pos[0] , x) > dis(pos[0] , y);return x.a < y.a;});vector<int>st(n + 1);int top = 0;st[++ top] = 0;st[++ top] = 1;for(int i = 2;i < n;i ++) {//维护栈while(top >= 2 && cross({pos[st[top]].x - pos[st[top - 1]].x , pos[st[top]].y - pos[st[top - 1]].y} , {pos[i].x - pos[st[top]].x , pos[i].y - pos[st[top]].y}) < 0) top --;st[++ top] = i;}long double ans = 0;int last = 0;for(int i = top;i >= 1;i --) {//计算凸包周长ans += dis(pos[st[i]] , pos[last]);last = st[i];}cout << fixed << setprecision(2) << ans << "\n";return;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;// cin >> t;while(t --) {solve();}return 0;
}
http://www.xdnf.cn/news/17685.html

相关文章:

  • Python 2025:最新技术趋势与展望
  • 每日五个pyecharts可视化图表-line:从入门到精通 (2)
  • lesson34:深入理解Python线程:从基础到实战优化
  • jupyter notebook如何打开其他盘目录
  • MCP学习与实践
  • [激光原理与应用-222]:机械 - 3D设计与2D设计的异同比较
  • Linux 虚拟机磁盘空间占满-全面清理方案
  • Cesium1.95中如何高效管理 1500 个高频实体
  • 赋值运算符指南
  • 代码可读性与维护性的实践与原则
  • word中,添加新的参考文献后,其他参考文献的交叉引用不能及时更新的解决办法
  • 《Webpack与Vite热模块替换机制深度剖析与策略抉择》
  • 二维前缀和问题
  • 如何在 Ubuntu 24.04 LTS Linux 上安装 MySQL 服务器
  • 电脑本地摄像头做成rtsp流调用测试windows系统中
  • 【大智慧数据】心智开花的时候
  • AI测试助手如何让Bug无处可藏
  • Dify 从入门到精通(第 26/100 篇):Dify 的知识图谱集成
  • 2025最新免费的大模型和免费的大模型API有哪些?(202508更新)
  • 2025年6月电子学会全国青少年软件编程等级考试(Python二级)真题及答案
  • 【Linux指南】Vim的全面解析与深度应用
  • C语言第八章指针四
  • 【接口自动化】初识pytest,一文讲解pytest的安装,识别规则以及配置文件的使用
  • Jotai:React轻量级状态管理新选择
  • Code Exercising Day 10 of “Code Ideas Record“:StackQueue part02
  • SQL三剑客:DELETE、TRUNCATE、DROP全解析
  • CentOS7.9 离线安装mysql数据库
  • CPP继承
  • Windows执行kubectl提示拒绝访问【Windows安装k8s】
  • `sk_buff` 结构体详解(包含全生命周期解析)