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算法并找到一组点最外侧的凸多边形。如果您觉得本文对您有所帮助,请不要吝啬您的点赞和评论,提供宝贵的反馈和建议,让更多的读者受益。

相关文章
|
8月前
|
开发框架 算法 搜索推荐
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
138 1
|
8月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
146 1
|
4天前
|
缓存 算法 安全
剖析‘共享文件夹只让指定用户看到’的 C# 精妙算法
在数字化时代,信息精准共享与管控至关重要。基于角色的访问控制(RBAC)算法通过将用户划分为不同角色并分配权限,确保“共享文件夹只让指定用户看到”。本文以C#代码为例,展示如何实现这一目标,并探讨大规模应用中的动态变更、性能优化和安全性挑战。RBAC算法结合C#编程,助力高效、安全的协作环境。
|
23天前
|
存储 监控 算法
企业内网监控系统中基于哈希表的 C# 算法解析
在企业内网监控系统中,哈希表作为一种高效的数据结构,能够快速处理大量网络连接和用户操作记录,确保网络安全与效率。通过C#代码示例展示了如何使用哈希表存储和管理用户的登录时间、访问IP及操作行为等信息,实现快速的查找、插入和删除操作。哈希表的应用显著提升了系统的实时性和准确性,尽管存在哈希冲突等问题,但通过合理设计哈希函数和冲突解决策略,可以确保系统稳定运行,为企业提供有力的安全保障。
|
2月前
|
算法 C#
C#常见的四种经典查找算法
C#常见的四种经典查找算法
|
2月前
|
算法 C# 索引
C#线性查找算法
C#线性查找算法!
|
3月前
|
存储 算法 C#
C#哈希查找算法
C#哈希查找算法
|
3月前
|
算法 C# 索引
C#二分查找算法
C#二分查找算法
|
3月前
|
机器学习/深度学习 算法 数据挖掘
使用C# 实现期望最大化算法
使用C# 实现期望最大化算法
68 0
|
4月前
|
存储 算法 C#
C#二叉搜索树算法
C#二叉搜索树算法