C# | 凸包算法之Jarvis,寻找一组点的边界/轮廓

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

image.png

C#实现凸包算法之Jarvis

@[toc]

前言

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

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

示例代码

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

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

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

            // 找到最左边的点
            PointD leftmostPoint = pointList[0];
            for (int i = 1; i < pointList.Count; i++)
            {
   
   
                if (pointList[i].X < leftmostPoint.X)
                {
   
   
                    leftmostPoint = pointList[i];
                }
            }

            // 使用栈来存储凸包的点
            Stack<PointD> hull = new Stack<PointD>();
            PointD currentPoint = leftmostPoint;
            do
            {
   
   

                hull.Push(currentPoint);
                PointD endpoint = pointList[0];
                for (int i = 1; i < pointList.Count; i++)
                {
   
   
                    if (endpoint.Equals(currentPoint) || Cross(currentPoint, endpoint, pointList[i]) < 0)
                    {
   
   
                        endpoint = pointList[i];
                    }
                }
                currentPoint = endpoint;
                pointList.Remove(currentPoint);
            } while (!currentPoint.Equals(leftmostPoint));

            return hull.ToArray();
        }

上面代码中定义了一个名为GetConvexHullByJarvis的静态方法,该方法接受一个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. 找到最左边的点,将它放入凸包中;
  2. 找到在当前点的右侧且离当前点最远的点,将它放入凸包中;
  3. 重复这个过程,直到回到最左边的点,形成一个闭合的凸多边形。

这就是 Jarvis 算法的基本思路。

需要注意的是,当点集中存在大量共线的点时,Jarvis 算法的时间复杂度可能会退化到 O(n^2) 级别,因为需要不断地扫描点集中的点来找到下一个点。此外当点集中存在大量重复的点时,Jarvis 算法可能会陷入死循环,因此需要对点集进行去重操作。


测试结果

用于测试的数据点:

        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 GetConvexHullByJarvis()
        {
   
   
            Console.WriteLine("Jarvis 算法");
            PrintPoints(ConvexHull.GetConvexHullByJarvis(points));
        }

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

执行结果如下:
image.png


结束语

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

相关文章
|
7月前
|
开发框架 算法 搜索推荐
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
134 1
|
7月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
128 1
|
1月前
|
算法 C#
C#常见的四种经典查找算法
C#常见的四种经典查找算法
|
1月前
|
算法 C# 索引
C#线性查找算法
C#线性查找算法!
|
2月前
|
存储 算法 C#
C#哈希查找算法
C#哈希查找算法
|
2月前
|
算法 C# 索引
C#二分查找算法
C#二分查找算法
|
2月前
|
机器学习/深度学习 算法 数据挖掘
使用C# 实现期望最大化算法
使用C# 实现期望最大化算法
51 0
|
3月前
|
存储 算法 C#
C#二叉搜索树算法
C#二叉搜索树算法
|
4月前
|
算法 数据可视化 搜索推荐
基于python的k-means聚类分析算法,对文本、数据等进行聚类,有轮廓系数和手肘法检验
本文详细介绍了基于Python实现的k-means聚类分析算法,包括数据准备、预处理、标准化、聚类数目确定、聚类分析、降维可视化以及结果输出的完整流程,并应用该算法对文本数据进行聚类分析,展示了轮廓系数法和手肘法检验确定最佳聚类数目的方法。
119 0
|
6月前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。