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()