[python刷题模板] 二维凸包(Andrew)

一、 算法&数据结构

1. 描述

凸多边形是指所有内角大小都在 [0,π] 范围内的 简单多边形。
在平面上能包含所有给定点的最小凸多边形叫做凸包。
实际上可以理解为用一个橡皮筋包含住所有给定点的形态。
  • 从给定的点集中,找出“最外围”的点集,这些点连成的凸多边形,可以包含集合中所有点。这个形状就是凸包。
  • Andrew算法可以用O(nlgn)时间求出凸包上所有的点(可以选择是否保留边上的点)。
  • 核心思想:
    • 按照(x,y)二维排序。
    • 显然p[0]和p[-1]都是凸包上的点,且是最左和最右的点。
    • 这两个点连线后,下半部分的所有凸包上的点和线段叫做下秃壳;同理上边是上秃壳。
    • 从p0出发逆时针访问秃壳上的点,发现所有遇到的新方向都是向左转(逆时针)。这个比较是指最后一条线和新线;因此可以用一个单调栈维护;那么剩余的问题就是如何判断方向,这可以用叉乘来算。
    • 先求下秃壳,从p0出发,从顺序遍历排好序的点,用单调栈维护下秃壳上的点,即a=st[-2],b=st[-1],c=p[i] (新点)。则判断ac是否在ab的右边,若是右边,说明ac在ab外围,b不是下秃壳上的点,弹出。
    • 同理求下秃壳,从p[-1]出发逆序遍历。
    • 注意由于上下秃壳不能复用点,因此需要一个额外数组used;
    • 注意求上秃壳时,弹栈的边界条件是栈大小>1;但求下秃壳时,栈大小要>m,其中m=下秃壳大小。
    • 同时注意最后求上秃壳时最后一个点是0,最后要弹出。即0算了两次。
  • 这个算法本质维护的是凸包的边。即栈中最后两个元素组成的直线方向 与 新边的方向比较。

2. 复杂度分析

  1. O(nlog2n)

3. 常见应用

  1. 求最大面积。
  2. 求最大周长。

4. 常用优化

  1. 凸包问题的五种解法。这里介绍了集中nb的算法。甚至还有在线算法,但看不懂。
  2. 三维凸包不会。

二、 模板代码

1. 二维凸包模板题。

例题: 587. 安装栅栏

  • 本题要求保留秃壳边上的点。
def substraction(a,b):
    # 向量a-向量b
    return a[0]-b[0],a[1]-b[1]
def cross(a,b):
    # 向量叉乘
    return a[0]*b[1] - a[1]*b[0]
def get_area(a,b,c):
    # 向量ab转为向量ac过程中扫过的面积;
    # 返回负代表ac在ab的左边,即逆时针旋转(向左)
    # 返回0代表共线;
    # 返回正代表ac在ab的右边,即顺时针旋转(向右)
    return cross(substraction(b,a),substraction(c,a))
def AnrewHull(points,keep_point_on_edges=False):
    n = len(points)
    if n < 4:
        return points
    points.sort()    

    hull = [0]
    used = [False] * n 
    
    def when_pop(a,b,c):  # 如果要保留边上的点,则>才弹栈
        return (get_area(a,b,c)>0) if keep_point_on_edges else (get_area(a,b,c)>=0)
    def make_hull(i,limit):
        while len(hull)>limit and when_pop(points[hull[-2]],points[hull[-1]],points[i]):
            used[hull.pop()] = False
        used[i] = True 
        hull.append(i)

    for i in range(1,n):
        make_hull(i,1)
    m = len(hull)
    for i in range(n-2,-1,-1):
        if not used[i]:
            make_hull(i,m)
    # hull.pop()
    return [points[i] for i in hull[:-1]]            
                

class Solution:
    def outerTrees(self, trees: List[List[int]]) -> List[List[int]]:               
        return AnrewHull(trees,True)

2. 先筛选秃壳上的点再后续计算

链接: 812. 最大三角形面积

  • 首先最大三角形三个点一定在凸包上;这一步可以筛选一部分点;但复杂度最坏还是O(n3)的。
  • 然后发现性质:固定i,j后,面积相对于k是秃曲线,有极点,最优k就是极点。
    • 固定i时,k和j是同向关系,因此可以双指针,优化掉一层维度。
    • 最终复杂度O(n2)。
def substraction(a,b):
    # 向量a-向量b
    return a[0]-b[0],a[1]-b[1]
def cross(a,b):
    # 向量叉乘
    return a[0]*b[1] - a[1]*b[0]
def get_area(a,b,c):
    # 向量ab转为向量ac过程中扫过的面积;
    # 返回负代表ac在ab的左边,即逆时针旋转(向左)
    # 返回0代表共线;
    # 返回正代表ac在ab的右边,即顺时针旋转(向右)
    return cross(substraction(b,a),substraction(c,a))
def AnrewHull(points,keep_point_on_edges=False):
    n = len(points)
    if n < 4:
        return points
    points.sort()    
    hull = [0]
    used = [False] * n 
    
    def when_pop(a,b,c):  # 如果要保留边上的点,则>才弹栈
        return (get_area(a,b,c)>0) if keep_point_on_edges else (get_area(a,b,c)>=0)
    def make_hull(i,limit):
        while len(hull)>limit and when_pop(points[hull[-2]],points[hull[-1]],points[i]):
            used[hull.pop()] = False
        used[i] = True 
        hull.append(i)

    for i in range(1,n):
        make_hull(i,1)
    m = len(hull)
    for i in range(n-2,-1,-1):
        if not used[i]:
            make_hull(i,m)
    # hull.pop()
    return [points[i] for i in hull[:-1]]
            
def calc_triangle_area(a,b,c):
    # 给三个点,求这三个点组成的三角形面积
    return abs(a[0]*b[1]+b[0]*c[1]+c[0]*a[1]-a[0]*c[1]-b[0]*a[1]-c[0]*b[1])/2 
class Solution:
    def largestTriangleArea(self, p: List[List[int]]) -> float:
        p = AnrewHull(p)
        n = len(p)
        # ans = 0
        # for i in range(n-2):
        #     for j in range(i+1,n-1):
        #         for k in range(j+1,n):
        #             ans = max(ans,calc_triangle_area(p[i],p[j],p[k]))
        # return max(calc_triangle_area(a,b,c) for a,b,c in combinations(p,3))
        ans = 0
        for i in range(n-2):
            k = i + 2
            for j in range(i+1,n-1):
                while k+1<n:
                    cur = calc_triangle_area(p[i],p[j],p[k])
                    nxt = calc_triangle_area(p[i],p[j],p[k+1])
                    if cur > nxt:
                        break 
                    k += 1
                ans = max(ans,calc_triangle_area(p[i],p[j],p[k]))
        return ans

三、其他

四、更多例题

五、参考链接

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值