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

18 篇文章 6 订阅

在这里插入图片描述

C#实现凸包算法之Jarvis

前言

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

执行结果如下:
在这里插入图片描述


结束语

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
凸包算法是一种计算给定集的凸包的方法。其中,Graham扫描算法Jarvis步进算法是两种常见且经典的凸包算法。 1. Graham扫描算法: - 首先,选择一个作为起始(通常选择最下方的,如果存在多个最下方的,则选择最左边的)。 - 将其余按照相对于起始的极角进行排序(逆时针排序)。 - 依次遍历排序后的集,对每个进行如下判断: - 如果当前与栈顶的两个构成的向量形成逆时针转向,将该入栈。 - 否则,将栈顶的出栈,直到当前与栈顶的两个构成的向量形成逆时针转向,然后将当前入栈。 - 遍历结束后,栈中剩余的即为凸包上的。 2. Jarvis步进算法(也称为Gift Wrapping算法): - 首先,选择一个起始(通常选择最左边的)作为凸包上的一个。 - 从起始开始,依次选择能够使得当前与下一个构成的向量形成逆时针转向的下一个,将其加入凸包。 - 重复上述过程,直到再次回到起始为止。 这两种算法都是基于极角的思想,通过不断地寻找相邻之间的逆时针转向来构建凸包。它们的时间复杂度都是O(nh),其中n是的个数,h是凸包上的的个数。相较而言,Graham扫描算法在一般情况下更快一些,但在特殊情况下可能会出现退化,而Jarvis步进算法则相对稳定但效率较低。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿长大人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值