C# | 凸包算法之Graham,快速找到一组点最外侧的凸多边形

news2025/1/10 11:00:50

在这里插入图片描述

C#实现凸包算法之Graham

文章目录

  • C#实现凸包算法之Graham
    • 前言
    • 示例代码
    • 实现思路
    • 测试结果
    • 结束语

前言

这篇关于凸包算法的文章,本文使用C#和Graham算法来实现凸包算法。
首先消除两个最基本的问题:

  1. 什么是凸包呢?
    凸包是一个包围一组点的凸多边形。凸多边形是指多边形中的每个内角都小于180度的多边形。
  2. 凸包算法有什么用呢?
    凸包算法的作用是找到这个凸多边形,并且使用最少的点来绘制出它的轮廓。凸包算法在计算机图形学、计算几何和机器学习等领域中有着广泛的应用。

示例代码

现在来看一下C#中的Graham算法是如何实现凸包算法的:

        /// <summary>
        /// 通过Graham算法获取围绕所有点的凸多边形的轮廓点<br/>
        /// </summary>
        /// <param name="points">点数组</param>
        /// <returns>轮廓点数组</returns>
        public static PointD[] GetConvexHullByGraham(PointD[] points)
        {
            if (points.Length < 3)
            {
                throw new ArgumentException("凸包算法需要至少3个点");
            }

            List<PointD> pointList = new List<PointD>(points);

            // 找到最下面的点
            PointD lowestPoint = pointList[0];
            for (int i = 1; i < pointList.Count; i++)
            {
                if (pointList[i].Y < lowestPoint.Y || (pointList[i].Y == lowestPoint.Y && pointList[i].X < lowestPoint.X))
                {
                    lowestPoint = pointList[i];
                }
            }

            // 将最下面的点作为起点,并按照极角排序其他点
            pointList.Remove(lowestPoint);
            pointList.Sort((p1, p2) =>
            {
                double angle1 = Math.Atan2(p1.Y - lowestPoint.Y, p1.X - lowestPoint.X);
                double angle2 = Math.Atan2(p2.Y - lowestPoint.Y, p2.X - lowestPoint.X);
                if (angle1 < angle2)
                {
                    return -1;
                }
                else if (angle1 > angle2)
                {
                    return 1;
                }
                else
                {
                    double distance1 = Math.Sqrt(Math.Pow(p1.X - lowestPoint.X, 2) + Math.Pow(p1.Y - lowestPoint.Y, 2));
                    double distance2 = Math.Sqrt(Math.Pow(p2.X - lowestPoint.X, 2) + Math.Pow(p2.Y - lowestPoint.Y, 2));
                    if (distance1 < distance2)
                    {
                        return -1;
                    }
                    else
                    {
                        return 1;
                    }
                }
            });

            // 使用栈来存储凸包的点
            Stack<PointD> hull = new Stack<PointD>();
            hull.Push(lowestPoint);
            hull.Push(pointList[0]);
            for (int i = 1; i < pointList.Count; i++)
            {
                PointD top = hull.Pop();
                while (hull.Any() && Cross(hull.Peek(), top, pointList[i]) <= 0)
                {
                    top = hull.Pop();
                }
                hull.Push(top);
                hull.Push(pointList[i]);
            }

            return hull.ToArray();
        }

上面代码中定义了一个名为GetConvexHullByGraham的静态方法,该方法接受一个PointD类型的数组作为输入参数,并返回一个PointD类型的数组,表示围绕所有点的凸多边形的轮廓点。

补充一下,关于示例中使用到的Cross方法和PointD类型的源代码如下:

        /// <summary>
        /// 计算从 a 到 b 再到 c 的叉积
        /// </summary>
        /// <returns>叉积值</returns>
        private static double Cross(PointD a, PointD b, PointD c)
        {
            return (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
        }
    public struct PointD 
    {
        public PointD(double x, double y) 
        {
            X = x;
            Y = y;
        }

        public double X { get; set; }
        public double Y { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
            {
                return false;
            }

            PointD other = (PointD)obj;
            return X.Equals(other.X) && Y.Equals(other.Y);
        }
    }

实现思路

  1. 检查输入的点数是否小于3个,因为凸包算法需要至少3个点才能计算出凸多边形。
  2. 找到数组中最下面的点,并将其作为起点。
  3. 对其余的点按照它们与起点之间的极角进行排序(这里使用了Atan2函数来计算点与起点之间的极角)。如果极角相同,则按照点与起点之间的距离进行排序。
  4. 使用一个栈来存储凸包的点。首先将起点和第一个排序后的点放入栈中。
  5. 遍历其余的点,如果遍历到的点与栈顶的两个点所形成的向量不是一个“左拐”,就将栈顶的点弹出,直到遍历到的点形成的向量是一个“左拐”。
  6. 将所有剩余的点都压入栈中,这些点就是凸多边形的轮廓点。

测试结果

用于测试的数据点:

        static PointD[] points = new PointD[]
        {   
                new PointD(0, 0),
                new PointD(0, 10),
                new PointD(10, 10),
                new PointD(10, 0),
                new PointD(1, 0),

                new PointD(4, 3),
                new PointD(5, 2),
                new PointD(6, 5),
                new PointD(4, 9),
                new PointD(4, 2),

                new PointD(5, 1),
                new PointD(6, 5),
                new PointD(1, 3),
                new PointD(7, 2),
                new PointD(8, 2),

                new PointD(6, 7),
                new PointD(8, 5),
                new PointD(9, 3),
                new PointD(7, 8),
                new PointD(8, 9),
        };

测试代码如下:

        [TestMethod]
        public void GetConvexHullByGraham()
        {
            Console.WriteLine("Graham 算法");
            PrintPoints(ConvexHull.GetConvexHullByGraham(points));
        }

        private void PrintPoints(PointD[] points)
        {
            Console.WriteLine(points.Select(p => $"  ({p.X}, {p.Y})").Join("\r\n"));
        }

执行结果如下:
在这里插入图片描述


结束语

通过本章的代码可以轻松实现Graham算法并找到一组点最外侧的凸多边形。如果您觉得本文对您有所帮助,请不要吝啬您的点赞和评论,提供宝贵的反馈和建议,让更多的读者受益。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IIC协议

1.认识IIC 1、IIC协议概述&#xff1a; IIC&#xff08;Inter-Integrated Circuit&#xff0c;集成电路总线&#xff09;是一种串行通信协议&#xff0c;也被称为I2C协议。它是由荷兰的PHILIPS公司&#xff08;现在philips公司将其半导体部门拆分出来并更名为NXP半导体公司&a…

KVM虚拟化技术学习-KVM管理

二&#xff0c;KVM管理 1.升级配置 1.创建一个空磁盘卷 [rootlocalhost ~]# qemu-img create -f qcow2 /kvm/images/disk2.qcow2 5G Formatting disk2.qcow2, fmtqcow2 size5368709120 encryptionoff cluster_size65536 lazy_refcountsoff 2.修改配置文件 <disk typefi…

SpringCloudAlibaba整合分布式事务Seata

文章目录 1 整合分布式事务Seata1.1 环境搭建1.1.1 Nacos搭建1.1.2 Seata搭建 1.2 项目搭建1.2.1 项目示意1.2.2 pom.xml1.2.2.1 alibaba-demo模块1.2.2.2 call模块1.2.2.3 order模块1.2.2.4 common模块 1.2.3 配置文件1.2.3.1 order模块1.2.3.2 call模块 1.2.4 OpenFeign调用1…

想要成为一个性能测试工程师需要掌握哪些知识?

如果想要成为一个性能测试工程师需要掌握哪些知识&#xff1f; 可以看看下方教程&#xff01; 2023年最新版Jmeter性能测试项目实战讲解&#xff0c;从入门到精通价值8888的实战教程_哔哩哔哩_bilibili2023年最新版Jmeter性能测试项目实战讲解&#xff0c;从入门到精通价值888…

idea不识别yml文件了

添加上这两个就好了

recurdyn实用操作

目录 1.剖视图查看 2.自动重复操作 3.多个面生成FaceSurface 4.查看质心&#xff0c;质量坐标工具Mass 5.履带仿真建立其他特征路面 6.joint单位 7.创建样条插值函数AKISPL 8.导出结果曲线数据 9.后处理各名称含义 1.剖视图查看 取消剖视图需要重新进入&#xff0c;取…

Redis的ZipList和QuickList和SkipList和RedisObject(未完成)

ZipList:压缩列表&#xff0c;为了节省内存而设计的一种数据结构 ZipList是一种特殊的双端链表&#xff0c;是由一系列的特殊编码的连续内存块组成&#xff0c;不需要通过指针来进行寻址来找到各个节点&#xff0c;可以在任意一端进行压入或者是弹出操作&#xff0c;并且该操作…

C# | 凸包算法之Andrew‘s,获取围绕一组点的凸多边形的轮廓点

C#实现凸包算法之Andrew’s 文章目录 C#实现凸包算法之Andrews前言示例代码实现思路测试结果结束语 前言 这篇关于凸包算法的文章&#xff0c;本文使用C#和Andrew’s算法来实现凸包算法。 首先消除两个最基本的问题&#xff1a; 什么是凸包呢&#xff1f; 凸包是一个包围一组…

上海城市开发者社区小聚有感

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是Rockey&#xff0c;不知名企业的不知名Java开发工程师 &#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&#x1f44d;一下博主哦 &#x1f4dd;联系方式&#xff1a;he18339193956&…

JAVAWEB(上)

一、HTML和CSS 1.盒子 2.表单 3.机器人回答&#xff1a; 3.1 label标签 <label>标签用于关联表单元素和文本标签&#xff0c;通过为表单元素定义文本标签&#xff0c;可以使表单更易于使用和访问。它的基本语法如下&#xff1a;<label for"input_id">…

LeetCode高频算法刷题记录9

文章目录 1. 二叉树的最大深度【简单】1.1 题目描述1.2 解题思路1.3 代码实现 2. 对称二叉树【简单】2.1 题目描述2.2 解题思路2.3 代码实现 3. 二叉树的直径【简单】3.1 题目描述3.2 解题思路3.3 代码实现 4. 验证二叉搜索树【中等】4.1 题目描述4.2 解题思路4.3 代码实现 5. …

基于51单片机的项目作品汇总

篇记录下自己做的项目作品&#xff0c;作品有实物也有仿真&#xff0c;以实物居多&#xff0c;主要是以单片机为主&#xff0c;单片机有HC32,STM32,STC,51等&#xff0c;本人从事单片机行业5年&#xff0c;拥有丰富的经验。也涉及QT&#xff0c;LVGL&#xff0c;嵌入式&#xf…

Java学习笔记20——常用API

常用API 常用APIMath类Math的常用方法 System类System类常用方法 Object类Object类常用方法 Arrays类Arrays常用方法 基本类型包装类Integer类的概述和使用int和String的相互转换自动装箱和拆箱 日期类Date类Date类的常用方法 SimpleDateFormat类SimpleDateFormat的构造方法Sim…

C++ priority_queue

C priority_queue &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了优先队列的对应接口的使用 文章目录…

APT(Advanced Persistent Threat高级持续性威胁)——网络安全

APT&#xff08;高级持续性威胁&#xff09; 特点攻击过程防御策略与APT相关的加密技术&#xff08;学习侧重&#xff09; 网络安全APT&#xff08;Advanced Persistent Threat高级持续性威胁&#xff09;是一种复杂的网络攻击&#xff0c;旨在长期潜伏在目标网络中&#xff0c…

【线程池】Java线程池的核心参数

目录 一、简介 二、构造方法 三、线程池的核心参数 3.1 corePoolSize 线程池核心线程大小 3.2 maximumPoolSize 线程池最大线程数量 3.3 keepAliveTime 空闲线程存活时间 3.4 unit 空间线程存活时间单位 3.5 workQueue 工作队列 ①ArrayBlockingQueue ②LinkedBlocki…

嵌入式Linux中pinctrl 子系统和 gpio 子系统分析

目录 1、gpio 子系统 API 2、pinctrl 子系统 API 本文讲解 pinctrl 子系统和 gpio 子系统的 API&#xff0c;以及使用示例。 传统的配置 pin 的方式就是直接操作相应的寄存器&#xff0c;但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为…

Linux :: 【基础指令篇 :: 文件及目录操作:(2)】::Linux操作系统的文件“框架”、绝对路径与相对路径及路径定位文件对象的解释

前言&#xff1a;本篇是 Linux 基本操作篇章的内容&#xff01; 笔者使用的环境是基于腾讯云服务器&#xff1a;CentOS 7.6 64bit。 学习集&#xff1a; C 入门到入土&#xff01;&#xff01;&#xff01;学习合集Linux 从命令到网络再到内核&#xff01;学习合集 本篇内容&am…

Linux常见IO模型

这篇博客开始我们Linux的最后一个章节--常见IO模型&#xff0c;在之前的博客当中我们讲述过Linux中基础的IO操作&#xff0c;欢迎大家去阅读。 我们通常指的IO操作便是数据的输入和输出&#xff0c;对应的具体操作过程我们可以将其分为两个步骤&#xff1a;等待IO就绪和数据拷…

Eclipse教程 Ⅵ

今天分享Eclipse Java 构建路径、Eclipse 运行配置(Run Configuration)和Eclipse 运行程序 Eclipse Java 构建路径 设置 Java 构建路径 Java构建路径用于在编译Java项目时找到依赖的类&#xff0c;包括以下几项&#xff1a; 源码包项目相关的 jar 包及类文件项目引用的的类…