凸包算法GrahamScan(C#代码)

1.什么是凸包

凸包是包裹给定点集的凸多边形,其中多边形的边界由点集中的点构成。凸包问题的解决对于计算机图形学、计算几何等领域非常重要。

2.凸包问题的应用场景

凸包在计算机图形中的应用非常广泛,通常我们可以利用一组点集的外轮廓去进行其他的操作,例如检测区域相交、覆盖范围等。

3.凸包问题如何求解

二维凸包的求解方法有多种,例如GrahamScanJarvis算法等,GrahamScan是一种利用扫描思想的算法,这里将对GrahamScan进行详细介绍。

时间复杂度为:O(nlogn)

数据结构:

主要思路如下:

(1)初始化起点,找出点集P中Y坐标最小的点p0作为起点,其一定在凸包上

(2)以p0为坐标起点,对剩下点集按照极角排序

(3) 将p1压入栈,依次遍历其他顶点

(4)当前顶点为p[i],如果p[i]在向量p[i-2]p[i-1]的左侧,则p[i]压入栈中

(5)判断三点走向,形成逆时针顺序。当前顶点为p[i],如果p[i]在向量p[i-2]p[i-1]的右侧,代表三点是顺时针走向,不是我们想要的走向,p[i-1]弹出栈顶。继续判断p[i]是否在相邻p[i-3]p[i-2]的左侧,如果在左侧,则代表三点是逆时针走向,满足要求,p[i]入栈,否则p[i-2]出栈,直至p[i]入栈。

(6)循环遍历剩下的点集,至此,我们可以完整找出点集的凸包。

4. 代码讲解

4.1 首先定义一个简单的 Point 类,表示平面上的一个点,有 X 和 Y 坐标。

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

4.2 定义一个 ConvexHull 类,其中包含一个用于计算两点之间极角的私有方法 Angle

public class ConvexHull
{
    // 求极角,返回角度的弧度值
    private static double Angle(Point p1, Point p2)
    {
        return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
    }

一个私有的比较器类 PolarAngleComparer,用于在排序时按照极角比较点

// 用于排序的比较器,按照极角排序
private class PolarAngleComparer : IComparer<Point>
{
    private Point referencePoint;

    public PolarAngleComparer(Point referencePoint)
    {
        this.referencePoint = referencePoint;
    }

    public int Compare(Point p1, Point p2)
    {
        // ... 省略排序比较器中的代码
    }
}

定义了一个私有的方法 Orientation,用于判断三个点的走向,帮助确定是否需要进行栈的出栈操作

// 判断三个点的走向,用于确定是否需要进行栈的出栈操作
private static int Orientation(Point p, Point q, Point r)
{
    // ... 省略判断走向的代码
}

算法入口:GrahamScan 方法,这是实际执行凸包算法的部分,以及 PrintConvexHull 方法,用于输出凸包的顶点

// Graham 扫描算法
public static List<Point> GrahamScan(List<Point> points)
{
    // ... 省略 Graham 扫描算法中的代码
}

// 输出凸包的顶点
public static void PrintConvexHull(List<Point> convexHull)
{
    // ... 省略输出凸包顶点的代码
}

最后我们来看看算法运行效果

5.完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _01_ConvexHull
{

    public class Point
    {
        public double X { get; set; }
        public double Y { get; set; }

        public Point(double x, double y)
        {
            X = x;
            Y = y;
        }
    }

    public class ConvexHull
    {
        // 求极角,返回角度的弧度值
        private static double Angle(Point p1, Point p2)
        {
            return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
        }

        // 用于排序的比较器,按照极角排序
        private class PolarAngleComparer : IComparer<Point>
        {
            private Point referencePoint;

            public PolarAngleComparer(Point referencePoint)
            {
                this.referencePoint = referencePoint;
            }

            public int Compare(Point p1, Point p2)
            {
                double angle1 = Angle(referencePoint, p1);
                double angle2 = Angle(referencePoint, p2);

                if (angle1 < angle2)
                    return -1;
                else if (angle1 > angle2)
                    return 1;
                else
                {
                    // 如果两点的极角相同,距离较近的排在前面
                    double distance1 = Math.Pow(p1.X - referencePoint.X, 2) + Math.Pow(p1.Y - referencePoint.Y, 2);
                    double distance2 = Math.Pow(p2.X - referencePoint.X, 2) + Math.Pow(p2.Y - referencePoint.Y, 2);

                    if (distance1 < distance2)
                        return -1;
                    else if (distance1 > distance2)
                        return 1;
                    else
                        return 0;
                }
            }
        }

        // 判断三个点的走向,用于确定是否需要进行栈的出栈操作
        private static int Orientation(Point p, Point q, Point r)
        {
            double val = (q.Y - p.Y) * (r.X - q.X) - (q.X - p.X) * (r.Y - q.Y);

            if (val == 0)
                return 0;  // 三点共线
            return (val > 0) ? 1 : 2; // 顺时针或逆时针
        }

        // Graham 扫描算法
        public static List<Point> GrahamScan(List<Point> points)
        {
            int n = points.Count;
            if (n < 3)
                throw new ArgumentException("凸包需要至少三个点");

            // 寻找最下方且最左边的点
            Point referencePoint = points.OrderBy(p => p.Y).ThenBy(p => p.X).First();

            // 根据极角排序其他点
            List<Point> sortedPoints = points.Where(p => p != referencePoint).ToList();
            sortedPoints.Sort(new PolarAngleComparer(referencePoint));

            // 压入参考点和前两个点
            Stack<Point> hull = new Stack<Point>();
            hull.Push(referencePoint);
            hull.Push(sortedPoints[0]);
            hull.Push(sortedPoints[1]);

            // 处理剩余的点
            for (int i = 2; i < sortedPoints.Count; i++)
            {
                while (hull.Count > 1 && Orientation(hull.ElementAt(1), hull.Peek(), sortedPoints[i]) != 2)
                    hull.Pop();
                hull.Push(sortedPoints[i]);
            }

            return hull.ToList();
        }



    }
}

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值