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

news/2024/7/21 7:45:07 标签: 算法, c#, 开发语言, .net, 图像处理

在这里插入图片描述

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.niftyadmin.cn/n/369857.html

相关文章

【如何在Java中使用ForkJoinPool】

目录 背景1.使用ForkJoinPool的线程池2.工作窃取算法3.ForkJoinPool的主要类4.使用递归操作5.资源任务6.何时使用ForkJoinPool7.总结 背景 使用ForkJoinPool去分解计算密集型任务且且并行地执行他们以获得更好的Java应用程序的性能。 ForkJoinPool是一个功能强大的Java类&…

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

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

【图】概念、存储结构、广度优先遍历遍历、深度优先遍历 - 详解

目录 前言 一、图 1.1、基本概念 二、图的存储结构 2.1、存储结构 2.1、邻接矩阵&#xff08;考察重点&#xff09; 2.1.1、代码实现 2.2、邻接表 2.3.1、无向邻接表存储 2.3.2、有向图邻接表存储 3.1、图的广度优先遍历&#xff08;层序遍历&#xff09; 3.2、图的…

本地创建的项目托管到git

本地创建的项目托管到git 这里的情况是本地先通过命令在电脑上指定文件夹创建好项目后&#xff0c;需要托管到git上&#xff0c;这里以gitee为例 打开gitee&#xff0c;登录滑动到右上方&#xff0b;&#xff0c;点击新建仓库&#xff0c;跳转到新建仓库页面 填写仓库信息&am…

⑥电子产品拆解分析-食物电子秤

⑥电子产品拆解分析-食物电子秤 一、功能介绍二、电路分析以及器件作用三、原理图复现与学习1、电源电路2、按键电路3、其它接口电路 一、功能介绍 ①高精度0.1g称重&#xff1b;②内置锂电池和外加2个7号电池超长续航&#xff1b;③可进行克和盎司单位称重&#xff1b;④一键智…

JAVA之数组2

添加元素 从后往前进行迭代&#xff0c;最后在末尾插入元素 tip&#xff1a;为避免数字在覆盖过程中丢失&#xff0c;故从后往前覆盖 删除元素 从前往后迭代&#xff0c;最后将末尾赋值为0 tip: 以覆盖的数arr【i】为基准&#xff0c;构造循环 共同处Tip: 范围均为【index1&…

【PyQt5】指示灯显示

【PyQt5】指示灯显示 1、背景2、代码示例3、QtDesigner绘制 1、背景 利用Qt5写工业控制软件交互界面的时候&#xff0c;经常需要在界面上有指示灯功能。 例如下面的明暗表示串行端口的连接和断开。 我们本质是用Qt5的label文本标签来实现的&#xff0c;即通过设置标签的样式表…

C919用了哪些人工智能(AI)技术?

#国产大飞机C919商业首飞#近日&#xff0c;C919在国人的期盼下终于迎来了首次商飞&#xff0c;机票已公开售卖。众所周知&#xff0c;C919是一款全新的、先进的大飞机&#xff0c;那你知道它采用了哪些新的人工智能&#xff08;AI&#xff09;技术吗&#xff1f;下面让我来为大…