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

简介: 这篇关于凸包算法的文章,本文使用C#和Andrew’s算法来实现凸包算法。首先消除两个最基本的问题:什么是凸包呢?凸包是一个包围一组点的凸多边形。凸多边形是指多边形中的每个内角都小于180度的多边形。凸包算法有什么用呢?凸包算法的作用是找到这个凸多边形,并且使用最少的点来绘制出它的轮廓。凸包算法在计算机图形学、计算几何和机器学习等领域中有着广泛的应用。

image.png

C#实现凸包算法之Andrew's

@[toc]

前言

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

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

示例代码

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

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

            // 按 x 坐标对点进行排序
            Array.Sort(points, (p1, p2) => p1.X.CompareTo(p2.X));

            // 创建下凸壳
            var lowerHull = new List<PointD>();
            foreach (var point in points)
            {
   
   
                while (lowerHull.Count >= 2 &&
                       Cross(lowerHull[lowerHull.Count - 2], lowerHull[lowerHull.Count - 1], point) <= 0)
                {
   
   
                    lowerHull.RemoveAt(lowerHull.Count - 1);
                }
                lowerHull.Add(point);
            }

            // 创建上凸壳
            var upperHull = new List<PointD>();
            for (int i = points.Length - 1; i >= 0; i--)
            {
   
   
                var point = points[i];
                while (upperHull.Count >= 2 &&
                       Cross(upperHull[upperHull.Count - 2], upperHull[upperHull.Count - 1], point) <= 0)
                {
   
   
                    upperHull.RemoveAt(upperHull.Count - 1);
                }
                upperHull.Add(point);
            }

            // 合并下凸壳和上凸壳
            if (lowerHull.Count == 1 && upperHull.Count == 1)
            {
   
   
                // 如果只有一个点,则返回原始点数组
                return points;
            }
            else
            {
   
   
                lowerHull.RemoveAt(lowerHull.Count - 1);
                upperHull.RemoveAt(upperHull.Count - 1);
                lowerHull.AddRange(upperHull);
                return lowerHull.ToArray();
            }
        }

上面代码中定义了一个名为GetConvexHullByAndrews的静态方法,该方法接受一个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. 按照点的 x 坐标对点进行排序,这样可以方便地找到下凸壳和上凸壳。
  2. 创建下凸壳和上凸壳。
    • 对于下凸壳,从左到右遍历点集,对于每个点,如果它在当前凸壳的右侧,那么就可以将当前凸壳的最右侧的点删除,因为这个点不可能在凸包中。重复这个过程,直到当前点在凸壳的左侧为止,然后将这个点添加到凸壳中。
    • 对于上凸壳,从右到左遍历点集,思路与下凸壳相同。
  3. 将下凸壳和上凸壳合并起来,去掉重复的点,就得到了围绕所有点的凸多边形的轮廓点。

测试结果

用于测试的数据点:

        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 GetConvexHullByAndrews()
        {
   
   
            Console.WriteLine("Andrew's 算法");
            PrintPoints(ConvexHull.GetConvexHullByAndrews(points));
        }

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

执行结果如下:
image.png


结束语

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

相关文章
|
1月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
58 1
|
4月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
47 1
|
1月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
17 2
|
1月前
|
搜索推荐 C#
C#实现冒泡排序算法
C#实现冒泡排序算法
19 0
|
3月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
42 0
|
4月前
|
存储 算法 数据处理
C# | 上位机开发新手指南(十一)压缩算法
流式压缩 流式压缩是一种能够实时处理数据流的压缩方式,例如音频、视频等实时传输的数据。 通过流式压缩算法,我们可以边读取边压缩数据,并能够随时输出已压缩的数据,以确保数据的实时性和减少存储和传输所需的带宽。 块压缩 块压缩则是将数据划分为固定大小的块,在每个块内进行独立的压缩处理。块压缩通常适用于文件、存储、传输等离线数据处理场景。 字典压缩 字典压缩是一种基于字典的压缩算法,通过建立一个字典来存储一组重复出现的字符串,并将这些字符串替换成字典中相应的索引,从而减少数据的存储和传输。字典压缩算法可以更好地处理数据中的重复模式,因为它们可以通过建立字典来存储和恢复重复出现的字符串。
46 0
C# | 上位机开发新手指南(十一)压缩算法
|
4月前
|
算法 C# 数据安全/隐私保护
C# | 上位机开发新手指南(十)加密算法——ECC
本篇文章我们将继续探讨另一种非对称加密算法——ECC。 严格的说,其实ECC并不是一种非对称加密算法,它是一种基于椭圆曲线的加密算法,广泛用于数字签名和密钥协商。 与传统的非对称加密算法(例如RSA)不同,ECC算法使用椭圆曲线上的点乘法来生成密钥对和进行加密操作,而不是使用大数分解等数学算法。这使得ECC算法具有相同的安全性和强度,但使用更少的位数,因此在资源受限的环境中具有优势。 ECC算法虽然使用公钥和私钥进行加密和解密操作,但是这些操作是基于点乘法实现的,而不是基于大数分解等算法实现的。因此,ECC算法可以被视为一种非对称加密算法的变体,但是它与传统的非对称加密算法有所不同。
135 0
C# | 上位机开发新手指南(十)加密算法——ECC
|
4月前
|
XML 算法 安全
C# | 上位机开发新手指南(九)加密算法——RSA
RSA的特性 非对称性 RSA算法使用公钥和私钥两个不同的密钥,公钥用于加密数据,私钥用于解密数据。公钥可以公开,任何人都可以使用,而私钥只有密钥持有人可以访问。 安全性 RSA算法基于大数分解难题,即将一个大的合数分解成其质数因子的乘积。由于目前没有有效的算法可以在合理的时间内对大质数进行分解,因此RSA算法被认为是一种安全的加密算法。 可逆性 RSA算法既可以用于加密,也可以用于解密。加密和解密都是可逆的过程,只要使用正确的密钥,就可以还原原始数据。 签名 RSA算法可以用于数字签名,用于验证数据的完整性和真实性。签名过程是将数据使用私钥进行加密,验证过程是将签名使用公钥进行解密。
103 0
C# | 上位机开发新手指南(九)加密算法——RSA
|
4月前
|
算法 搜索推荐 安全
C# | 上位机开发新手指南(八)加密算法——AES
AES——这是在加密算法中相当重要的一种加密方式! 虽然这个世界上已经存在了非对称加密算法(比如RSA、ECC等),但是在对称加密算法中,AES的地位依然相当重要。与非对称加密算法不同,对称加密算法使用的是相同的密钥对数据进行加密和解密,因此其加密和解密速度更快,而且更加高效。而在对称加密算法中,AES是目前最安全、最可靠的加密算法之一,其加密强度和运行效率都非常高。因此,无论是在个人计算机、移动设备,还是在服务器和云计算等领域,AES都被广泛应用于数据的加密和解密过程中。
95 0
C# | 上位机开发新手指南(八)加密算法——AES
|
4月前
|
存储 算法 安全
C# | 上位机开发新手指南(七)加密算法
加密算法是信息安全领域中的重要技术之一,可以保护数据在传输、存储和处理过程中的安全性。 学习加密算法可以帮助我们更好地理解和应用其他相关技术。例如,数字证书、数字签名、安全协议等都与加密算法密切相关,掌握加密算法可以为我们理解和应用这些技术提供帮助。
54 0
C# | 上位机开发新手指南(七)加密算法