Python-Graham扫描算法-凸包问题

1.问题描述

某大学ACM集训队,不久前向学校申请了一块空地,成为自己的果园。全体队员兴高采烈的策划方案,种植了大批果树,有梨树、桃树、香蕉……。后来,发现有些坏蛋,他们暗地里偷摘果园的果子,被ACM集训队队员发现了。因此,大家商量解决办法,有人提出:修筑一圈篱笆,把果园围起来,但是由于我们的经费有限,必须尽量节省资金,所以,我们要找出一种最合理的方案。由于每道篱笆,无论长度多长,都是同等价钱。所以,大家希望设计出来的修筑一圈篱笆的方案所花费的资金最少。有人已经做了准备工序哦,统计了果园里果树的位置,每棵果树分别用二维坐标来表示,进行定位。现在,他们要求根据所有的果树的位置,找出一个n边形的最小篱笆,使得所有果树都包围在篱笆内部,或者在篱笆边沿上。

2.方法思路

本题的实质:凸包问题。

选用了Graham扫描算法,根据之前学过的数学原理我们知道y值最小的坐标点一定在构成的凸包上,首先找到y坐标的点为枢纽点(如果有多个点拥有最小 y 坐标,则选择最左边的点),将其余坐标点与枢纽点的极角(与x轴正方向的夹角)从小到大排序(如果两点有相同的极角,则将距离枢纽点较远的排在前面),然后用一个栈,将枢纽点和排好序的第一个点入栈,依次将剩余的点与栈顶的两点做叉乘比较,最后输出比较后的凸包上的点。

3.源代码

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
plt.rcParams['font.sans-serif'] = [font.get_name()]  # 在绘制时使用的默认字体

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def distance(a, b):
    return np.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)

def cross_product(a, b, c):
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)

def compare_angles(pivot, p1, p2):
    orientation = cross_product(pivot, p1, p2)
    if orientation == 0:
        return distance(pivot, p1) - distance(pivot, p2)
    return -1 if orientation > 0 else 1

def graham_scan(points):
    n = len(points)
    if n < 3:
        return "凸包需要至少3个点"

    pivot = min(points, key=lambda point: (point.y, point.x))
    points = sorted(points, key=lambda point: (np.arctan2(point.y - pivot.y, point.x - pivot.x), -point.y, point.x))

    stack = [points[0], points[1], points[2]]
    for i in range(3, n):
        while len(stack) > 1 and compare_angles(stack[-2], stack[-1], points[i]) > 0:
            stack.pop()
        stack.append(points[i])

    return stack

def plot_convex_hull(points, convex_hull):
    plt.figure()
    plt.scatter([p.x for p in points], [p.y for p in points], color='b', label="所有点")

    # 绘制凸包
    plt.plot([p.x for p in convex_hull] + [convex_hull[0].x], [p.y for p in convex_hull] + [convex_hull[0].y], linestyle='-', color='g', label="篱笆边")

    for i in range(len(convex_hull)):
        plt.plot([convex_hull[i].x, convex_hull[(i+1) % len(convex_hull)].x], [convex_hull[i].y, convex_hull[(i+1) % len(convex_hull)].y], linestyle='-', color='g')

    plt.legend(prop=font)  # 使用设置过的中文字体
    plt.show()

def main():
    n = int(input("请输入果树的数量:"))
    points = []
    for i in range(n):
        x, y = map(int, input(f"请输入第{i+1}颗果树的坐标(格式:x y):").split())
        points.append(Point(x, y))

    convex_hull = graham_scan(points)
    print("构成篱笆的果树坐标为:")
    for p in convex_hull:
        print(f"({p.x}, {p.y})")

    plot_convex_hull(points, convex_hull)

if __name__ == "__main__":
    main()

4.运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

西唯兵欧泡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值