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

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

image.png

C#实现凸包算法之Graham

@[toc]

前言

这篇关于凸包算法的文章,本文使用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"));
        }

执行结果如下:
image.png


结束语

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

相关文章
|
2月前
|
开发框架 算法 搜索推荐
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
86 1
|
2月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
73 1
|
9天前
|
Dart 算法 JavaScript
C#数据结构与算法入门教程,值得收藏学习!
C#数据结构与算法入门教程,值得收藏学习!
|
9天前
|
机器学习/深度学习 算法 搜索推荐
一个开源且全面的C#算法实战教程
一个开源且全面的C#算法实战教程
|
1月前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。
415 3
|
2月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
23 2
|
2月前
|
搜索推荐 C#
C#实现插入排序算法
C#实现插入排序算法
23 1
|
2月前
|
搜索推荐 C#
C#实现冒泡排序算法
C#实现冒泡排序算法
32 0
|
2月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
74 0
|
2月前
|
存储 算法 数据处理
C# | 上位机开发新手指南(十一)压缩算法
流式压缩 流式压缩是一种能够实时处理数据流的压缩方式,例如音频、视频等实时传输的数据。 通过流式压缩算法,我们可以边读取边压缩数据,并能够随时输出已压缩的数据,以确保数据的实时性和减少存储和传输所需的带宽。 块压缩 块压缩则是将数据划分为固定大小的块,在每个块内进行独立的压缩处理。块压缩通常适用于文件、存储、传输等离线数据处理场景。 字典压缩 字典压缩是一种基于字典的压缩算法,通过建立一个字典来存储一组重复出现的字符串,并将这些字符串替换成字典中相应的索引,从而减少数据的存储和传输。字典压缩算法可以更好地处理数据中的重复模式,因为它们可以通过建立字典来存储和恢复重复出现的字符串。
74 0
C# | 上位机开发新手指南(十一)压缩算法