【算法】Graham 凸包扫描算法 ( 凸包概念 | 常用的凸包算法 | 角排序 | 叉积 | Python 代码示例 )

文章目录

  • 一、Graham 凸包扫描算法
    • 1、凸包概念
    • 2、常用的凸包算法
    • 3、Graham 凸包扫描算法
  • 二、Graham 算法前置知识点
    • 1、角排序
    • 2、叉积
    • 3、算法过程分析
  • 三、代码示例
    • 1、完整代码示例
    • 2、执行结果


使用 Graham 算法绘制的凸包效果 :
在这里插入图片描述

博客代码下载 : https://download.csdn.net/download/han1202012/89428182

  • 使用 PyCharm 打开 , 使用 Python 3.9 开发 ;
    在这里插入图片描述




一、Graham 凸包扫描算法




1、凸包概念


凸包概念 : 在二维平面中 , 包围点集的最小凸多边形 , 其顶点集包含了给定点集中的所有点 , 并且不存在任何一条线段可以穿过这个多边形的内部而不与多边形的边界相交 ;

下图中 ,

  • 左侧的 P1 图是凸包 ;
  • 右侧的 P2 图不是凸包 , 因为该图中 , A2 到 B2 的点连接线与 凸多边形 的边界发生了相交 ;
    在这里插入图片描述

2、常用的凸包算法


常用的凸包算法有 :

  • Graham 扫描法
  • Jarvis 步进法
  • 快速凸包算法

3、Graham 凸包扫描算法


在二维平面上给出一个有限个点的点集 , 其坐标都为 (x , y) ;

Graham 格雷厄姆 凸包扫描算法 , 可以找到上述点集的 凸包边界 , 其时间复杂度是 O(nlogn) ;





二、Graham 算法前置知识点




1、角排序


角排序 是 以角度大小进行排序 , 这里的角度是 选定的基准点 与 点集中的点 的 极角 进行排序 ;

角排序 是一种在计算几何学和算法设计中常用的技术 , 用于对点集中的点按照其与某一基准点的极角进行排序 ;

极角 , 又称为 " 极坐标角度 " , 是指一个点相对于 极点 与 极轴 之间的夹角 , 极角通常用来描述点在 极坐标系 中的位置 ;

  • 极点 是 中心的点 ;
  • 极轴 是 水平 x 轴 ;

极坐标系如下图所示 , 一个点的位置由 极角 ( 从极轴到点到极点连线的方向的角度 ) 和 极径 ( 点到极点的距离 ) 确定 ;

在这里插入图片描述


在角排序中 , 极角是指从基准点出发到其他点的连线与某一固定方向的夹角 ;

角排序用于解决凸包算法中的子问题 , 例如 Graham 扫描算法中 , 需要对点集中的点按照其与基准点的极角进行排序 , 以便确定凸包的边界顺序 ;


在本算法中 , 以极坐标的原点为中心 , 进行角排序 ;


2、叉积


叉积 , 又称为 " 向量积 " 或 " 矢量积 " , 是两个向量之间的一种运算 , 叉积 的结果是一个新的向量 , 值为两个向量所张成的平行四边形的面积 , 方向垂直于这个平行四边形的平面 , 符合右手定则 ;

判断 B 点 在 向量 OA 的左侧还是右侧 :

  • B 在 向量 OA 左侧 , 则 OA 与 OB 的 叉积为负数 ;
  • B 在 向量 OA 右侧 , 则 OA 与 OB 的 叉积为正数 ;
    在这里插入图片描述

给定平面上 3 个点 ABC , 叉积 可以判断一个 点 C 在向量 AB 的哪一边 ,

  • 如果 C 点在 向量 AB 左边 , 则 AB 与 AC 的叉积为正 ;
  • 如果 C 点在 向量 AB 右边 , 则 AB 与 AC 的叉积为负 ;

3、算法过程分析


设置一个 栈 数据结构 ,

将左下角的 2 个点放入 栈 中 ,

从 第三个点开始循环 , 循环内容如下 :

先将要遍历的点放入 栈中 , 判断 新放入的点 是否在 栈顶 2 个元素组成的向量的左边 ,

  • 如果在左边 , 说明该点是凸包上的点 , 栈中保留该点 , 则继续遍历下一个点 ;
  • 如果在右边 , 说明该点不是凸包上的点 , 从栈中弹出该点 , 继续遍历下一个点 ;




三、代码示例



博客代码下载 : https://download.csdn.net/download/han1202012/89428182

  • 使用 PyCharm 打开 , 使用 Python 3.9 开发 ;

1、完整代码示例


import tkinter as tk    # 导入 Tkinter 模块
import random           # 导入 random 模块用于生成随机数
import math             # 导入 math 模块用于数学计算

# 定义点类
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# 计算两点之间的距离
def distance(p1, p2):
    return math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2)

# 通过计算叉乘计算 3 点方向
#   如果叉乘结果 = 0 , 则说明 p1/p2/p3 共线
#   如果叉乘结果 > 0 , 则为顺时针方向
#   如果叉乘结果 < 0 , 则为逆时针方向
def cross_product(p1, p2, p3):
    return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)

# 求点集的极角
def polar_angle(p0, p):
    return math.atan2(p.y - p0.y, p.x - p0.x)

# 计算两点之间的距离的平方
def distance_squared(p1, p2):
    return (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2

# 角排序函数
def angle_sort(points):
    # 点集个数必须大于 3 个
    if len(points) < 2:
        return points

    # 找到最左下方的点作为起始点
    # p0 点作为极坐标的原点
    p0 = min(points, key=lambda p: (p.y, p.x))
    # 按照极角和距离的平方排序
    # 先按照极角进行排序 如果极角相同 则按照 p0 点到该点的距离排序
    sorted_points = sorted(points, key=lambda p: (polar_angle(p0, p), distance_squared(p0, p)))
    # 返回按照极角进行排序的 Point 集合
    return [p0] + sorted_points[1:]

# Graham 扫描法找凸包
def graham_scan(points):
    if len(points) < 3:
        return points

    # 进行角排序
    sorted_points = angle_sort(points)
    # 栈数据结构
    stack = []
    for p in sorted_points:
        # 如果栈中的元素大于 2 个开始执行循环, 计算 p 点 在 栈顶两个元素(倒数第二个在前,倒数第一个再后)组成的向量的左侧还是右侧
        while len(stack) >= 2 and cross_product(stack[-2], stack[-1], p) <= 0:
            # 如果 p 点在栈顶两个元素组成的向量的左侧 则说明该点是凸边中的点 , 栈顶元素不是凸边中的点 , 将栈顶出栈 , 将本元素入栈
            stack.pop()
        # 向栈中添加新元素
        stack.append(p)
    return stack

# 生成随机点集
# 界面为 800x600 , x 方向上在 50 ~ 750 之间生成点, y 方向上在 50 ~ 550 之间生成点
def generate_points(num_points):
    points = []
    for _ in range(num_points):
        x = random.randint(50, 750)  # 生成 x 坐标范围在 50 到 750 之间的随机数
        y = random.randint(50, 550)  # 生成 y 坐标范围在 50 到 550 之间的随机数
        points.append(Point(x, y))
    return points

# 在画布上绘制点
def draw_points(canvas, points):
    for point in points:
        canvas.create_oval(point.x - 2, point.y - 2, point.x + 2, point.y + 2, fill="blue")  # 绘制圆点

# 在画布上绘制凸包
def draw_convex_hull(canvas, convex_hull):
    for i in range(len(convex_hull)):
        p1 = convex_hull[i]
        p2 = convex_hull[(i + 1) % len(convex_hull)]
        canvas.create_line(p1.x, p1.y, p2.x, p2.y, fill="red")  # 绘制凸包的边

# 主函数
def main():
    num_points = 100                       # 点的数量设置为 200 个
    points = generate_points(num_points)    # 生成随机点集
    convex_hull = graham_scan(points)  # 使用 Graham 扫描法找凸包

    root = tk.Tk()  # 创建 Tkinter 窗口
    root.title("Graham Scan Convex Hull")  # 设置窗口标题
    canvas = tk.Canvas(root, width=800, height=600, bg="white")  # 创建画布
    canvas.pack()  # 将画布放置在窗口中央

    draw_points(canvas, points)  # 绘制点集
    draw_convex_hull(canvas, convex_hull)  # 绘制凸包

    root.mainloop()  # 进入 Tkinter 主循环

if __name__ == "__main__":
    main()  # 调用主函数


2、执行结果


执行上述代码后 , 在画面中随机生成了 100 个点 , 并进行 Graham 扫描算法 , 计算出了点集的凸集 , 绘制效果如下 :

在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【编程语言】Python平台化为何比Java差?

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

[Shell编程学习路线]——if条件语句(单,双,多分支结构)详细语法介绍

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日7点50分 &#x1f004;️文章质量&#xff1a;95分 文章目录 ————前言———— &#x1f4af;趣站&#x1f4af…

AI大模型在运动项目的深度融合和在穿戴设备的实践及未来运动健康技术发展

文章目录 1. 技术架构2. 模型选择2.1 LSTM&#xff08;长短期记忆网络&#xff09;2.2 CNN&#xff08;卷积神经网络&#xff09;2.3 Transformer 3. 数据处理数据预处理 4. 实时性要求4.1 边缘计算4.2 模型优化 5. 数据隐私与安全6. 深入分析AI大模型在穿戴设备的应用和未来发…

Harbor镜像中心搭建

文章目录 Harbor镜像中心搭建前置条件下载Harbor创建CA证书配置Harbor开始启动地址映射访问配置本地登录配置外部虚拟机访问 Harbor镜像中心搭建 Harbor是一个镜像中心&#xff0c;我们所熟知的DockerHub就是一个镜像中心&#xff0c;我们可以把我们打包的镜像放在镜像中心中供…

Nuxt3 实战 (九):使用 Supabase 实现 Github 认证鉴权

前言 Supabase 使用的是 postgresql 的 Row Level Security (RLS)&#xff0c;可以限制不同用户对同一张表的不同数据行的访问权限。这种安全机制可以确保只有授权用户才能访问其所需要的数据行&#xff0c;保护敏感数据免受未授权的访问和操作。 Auth Providers 打开 Supab…

Latex的参考文献中显示三个问号???——解决办法

1、问题描述 在使用spring模板&#xff0c;并引用book时&#xff0c;末尾的引文地方出现三个???由于使用的bibtex是直接从谷歌学术中导出来的&#xff0c;其中仅包含作者&#xff0c;书名&#xff0c;出版社&#xff0c;年份等&#xff0c;缺少了重要的信息。结果导致在出版…

【ARM】MDK Debug模式下Disassembly窗口介绍

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 主要了解Disassembly窗口中包含的内容&#xff0c;和如何利用Disassembly中的内容了解程序的存储和调用情况。 2、 问题场景 对于Disassembly窗口中具体包含的内容不了解&#xff0c;无法合理地应用Disassembly窗口…

为何云原生是未来?企业IT架构的颠覆与重构(上)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是云原生 2、云原生的背景和起源 背景 起源 关…

SRM系统对供应商的意义是什么?

在甲方与乙方互相合作、沟通的世界里&#xff0c;供应商们也同样面临着诸多挑战~ 你是否经常感到在庞大的订单流中迷失方向&#xff0c;对库存情况一无所知&#xff0c;你是否在与采购商的沟通中频频碰壁&#xff1f;你是否在苦苦寻找一个能够全面管理供应商关系的系统&#x…

Dockerfile 自定义镜像

大家好 , 今天我要和大家分享一个现代软件开发中不可或缺的工具 - Docker . 在这个快速发展的技术时代 , 我们经常面临着应用部署的复杂性、环境差异以及不同操作系统之间的兼容性问题 . 这些问题不仅消耗大量时间 , 还可能导致项目延期和成本增加 . Docker 的出现解决了我们在…

群体优化算法---杂交进化算法介绍,模式识别结合粒子群优化PSO,使用最近邻KNN作为分类器

介绍 杂交进化算法&#xff08;Hybrid Evolutionary Algorithms, HEAs&#xff09;是一类结合了传统进化算法&#xff08;Evolutionary Algorithms, EAs&#xff09;和其他优化方法&#xff08;如局部搜索、模拟退火、禁忌搜索等&#xff09;的混合优化技术。其目的是通过融合…

【单片机】DS2431芯片,读写128个字节,程序

ds2431pt&r stm32读写程序&#xff1a; 部分程序&#xff1a; #include "sys.h" #include "delay.h" #include "usart.h"#include <stdio.h> #include <stdlib.h> #include <string.h>#include "sys.h" #incl…

SE语法总结博文(附思维导图)

Java中的规范 注释 //单行注释 /*多行注释 */ /**文档注释 */命名规范 命名时可以包含&#xff1a;字母、数字以及 下划线和 $ 符号等等。 但是不能以数字开头&#xff0c;也不能是关键字&#xff0c;且严格区分大小写。 类名&#xff1a;每个单词的首字母大写(大驼峰)&…

花钱就能过?PMP到底有没有用

在项目管理领域&#xff0c;PMP&#xff08;Project Management Professional&#xff09;认证常被看作是专业能力的金牌标准。 然而&#xff0c;伴随着这一认证的普及&#xff0c;也出现了一些质疑声&#xff0c;比如“PMP认证是否只是金钱和时间的投入就能获得的证书&#xf…

JavaWeb之JSP、EL表达式、JSTL标签

JSP JSP&#xff1a;Java Server Pages&#xff0c;是一种动态网页开发技术。它使用JSP标签在HTML网页中插入Java代码。标签通常以<%开头以%>结束。动态插值使用 <%值%> 的格式 jsp本质上就是servlet JSP九大内置对象 private JSPWriter out;//输出流对象 privat…

【代码随想录】【算法训练营】【第36天】[452]用最少数量的箭引爆气球 [435]无重叠区间 [763]划分字母区间

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 36&#xff0c;周三&#xff0c;最难坚持的一天~ 题目详情 [452] 用最少数量的箭引爆气球 题目描述 452 用最少数量的箭引爆气球 解题思路 前提&#xff1a;区间可能重叠 思路&#xff1a;…

【探索Linux命令行】从基础指令到高级管道操作的介绍与实践

目录 man 指令&#xff08;说明&#xff09; 介绍 cp 指令&#xff08;复制&#xff09; ​编辑 mv 指令&#xff08;移动&#xff09; ​编辑 cat 指令&#xff08;类似cout&#xff09; less&#xff08;查找&#xff09; head & tail&#xff08;打印&#xff…

【CT】LeetCode手撕—88. 合并两个有序数组

目录 题目1- 思路2- 实现⭐88. 合并两个有序数组——题解思路 2- ACM实现 题目 原题连接&#xff1a;88. 合并两个有序数组 1- 思路 模式识别 模式1&#xff1a;两个有序数组合并 ——> 双指针模式2&#xff1a;返回结果填充到 nums1[mn] ——> 需要开辟新的数组空间 …

Linux shell 重定向输入和输出

Linux shell 重定向输入和输出 1. Standard I/O streams2. Redirecting to and from the standard file handles (标准文件句柄的重定向)2.1. command > file2.2. command >> file2.3. command 2> file2.4. command 2>> file2.5. command < file2.6. comm…

餐饮食堂安全守护者:可燃气体报警器故障处理与检测要点解析

在餐饮食堂中&#xff0c;可燃气体报警器的正常运行对于预防火灾和保障人员安全至关重要。 接下来&#xff0c;佰德将围绕可燃气体报警器的故障现象识别、原因排查、安全操作准则、专业工具与备件、故障处理步骤、验证与测试以及维护与保养建议等方面进行详细阐述&#xff0c;…