之前写了一篇Python蛮力法解决凸包问题并用matplotlib实现可视化,最后也给出了同样是在1000个点的情况下蛮力法和分治法的差距有多大(蛮力法1154秒,分治法0.125秒…)
先解释一下为什么吧:
因为蛮力法的重点在于中间有三重循环,所以时间复杂度为O(n3),而分治法需要对点集进行一次排序还有一次遍历,排序算法的复杂度为O(logn),遍历一遍复杂度为O(n),所以分治法的时间复杂度为O(nlogn)。
二者相比,完全就不是一个档次啊…🌚
然后说说我的思路:
分治法,顾名思义,分而治之。
对于平面上的一个点集:
我们很容易找到最左边的点和最右边的点,将他们连起来,就会将整个点集分为上半部分和下半部分,同时这两个点也必定是凸包的边界点
接下来在其它点中找到两个点,使之与这两个点组成的三角形面积最大或最小(最大的是上半包的顶点;最小即为负的最大,说明这个点是下半包的顶点),并将这两个点添加到边界点集合中。
找到两个顶点后,继续对上/下半包的点进行递归遍历,对于上半包来说,以新形成的那两条边为底,如果有点新编围成的三角形面积为正,那就找到最大的继续分三角形;下班包也是一样的道理,只是下半包需要找最小的。同样将顶点添加到边界点集合中去。
完了继续递归,知道没有符合条件的为止。此时的边界点集合再加上最左边和最右边的两个点,就是凸包所有的边界点。
( BUT,你认为真实的过程就是这样的吗?不不不,这只是一个便于我们理解的过程,真实的递归过程是这样的:😏
如果你对这张图是怎么画出来的有兴趣,可以去我的这篇文章:分治法进阶篇 | 利用matplotlib画出凸包问题分治递归策略实现过程动态图看看<有源码欸>)
然后说代码:
随机生成具有n个点的点集:
def rand_point_set(n, range_min=0, range_max=101):
"""
随机生成具有 n 个点的点集
:param range_max: 生成随机点最小值,默认 0
:param range_min: 生成随机点最大值,默认 100
:param n: int
:return: list [(x1,y1)...(xn,yn)]
"""
try:
return list(zip([random.uniform(range_min, range_max) for _ in range(n)],
[random.uniform(range_min, range_max) for _ in range(n)]))
except IndexError as e:
print("\033[31m" + ''.join(e.args) + "\n输入范围有误!" + '\033[0m')
判断三角形的面积:
def calc_area(a, b, c):
"""
判断三角形面积
"""
x1, y1 = a
x2, y2 = b
x3, y3 = c
return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3
递归寻找上半部分的边界点:
def border_point_up(left_point, right_point, lists, border_points):
"""
寻找上半部分边界点
:param left_point: tuple, 最左边的点
:param right_point: tuple, 最右边的点
:param lists: 所有的点集
:param border_points: 边界点集
:return:
"""
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) > area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,item) > area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_up(left_point, max_point, lists, border_points)
border_point_up(max_point, right_point, lists, border_points)
递归寻找下半部分的边界点:
def border_point_down(left_point, right_point, lists, border_points):
"""
寻找下半部分边界点
:param left_point: tuple, 最左边的点
:param right_point: tuple, 最右边的点
:param lists: 所有的点集
:param border_points: 边界点集
:return:
"""
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) < area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,item) < area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_down(left_point, max_point, lists, border_points)
border_point_down(max_point, right_point, lists, border_points)
返回顺时针的边界点集:
def order_border(lists):
"""
返回顺时针的边界点集
:param lists: 无序边界点集
:return: list [( , )...( , )]
"""
lists.sort()
first_x, first_y = lists[0] # 最左边的点
last_x, last_y = lists[-1] # 最右边的点
list_border_up = [] # 上半边界
for item in lists:
x, y = item
if y > max(first_y, last_y):
list_border_up.append(item)
if min(first_y, last_y) < y < max(first_y, last_y):
if calc_area(lists[0], lists[-1], item) > 0:
list_border_up.append(item)
else:
continue
list_border_down = [_ for _ in lists if _ not in list_border_up] # 下半边界
list_end = list_border_up + list_border_down[::-1] # 最终顺时针输出的边界点
return list_end
matplotlib画图:
def draw(list_points, list_borders):
"""
画图
:param list_points: 所有点集
:param list_borders: 所有边界点集
:return: picture
"""
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
list_borders.append(list_borders[0])
for i in range(len(list_borders) - 1):
one_, oneI = list_borders[i]
two_, twoI = list_borders[i + 1]
plt.plot([one_, two_], [oneI, twoI])
plt.scatter(list_all_x, list_all_y)
plt.show()
最后写一个输入函数:
因为我的随机点集生成函数 rand_point_set(n, range_min=0, range_max=101) 指定的后两个参数是用来选择生成点集范围的,所以,这是我的输入函数:
def main():
"""
:return: 所有点
"""
inputs = list(map(int, input().split()))
if len(inputs) == 1:
return rand_point_set(inputs[0])
elif len(inputs) == 2:
return rand_point_set(inputs[0], inputs[1])
elif len(inputs) == 3:
return rand_point_set(inputs[0], inputs[1], inputs[2])
else:
print("\033[31m输入数据太多,请重新输入!\033[0m")
main()
最后把这些函数组合起来,
我把完整代码(复制可用的那种)贴到这里:
import random
import numpy as np
import matplotlib.pyplot as plt
def calc_area(a, b, c):
"""
判断三角形面积
"""
x1, y1 = a
x2, y2 = b
x3, y3 = c
return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3
def rand_point_set(n, range_min=0, range_max=101):
"""
随机生成具有 n 个点的点集
:param range_max: 生成随机点最小值,默认 0
:param range_min: 生成随机点最大值,默认 100
:param n: int
:return: list [(x1,y1)...(xn,yn)]
"""
try:
return list(zip([random.uniform(range_min, range_max) for _ in range(n)],
[random.uniform(range_min, range_max) for _ in range(n)]))
except IndexError as e:
print("\033[31m" + ''.join(e.args) + "\n输入范围有误!" + '\033[0m')
def border_point_up(left_point, right_point, lists, border_points):
"""
寻找上半部分边界点
:param left_point: tuple, 最左边的点
:param right_point: tuple, 最右边的点
:param lists: 所有的点集
:param border_points: 边界点集
:return:
"""
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) > area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,
item) > area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_up(left_point, max_point, lists, border_points)
border_point_up(max_point, right_point, lists, border_points)
def border_point_down(left_point, right_point, lists, border_points):
"""
寻找下半部分边界点
:param left_point: tuple, 最左边的点
:param right_point: tuple, 最右边的点
:param lists: 所有的点集
:param border_points: 边界点集
:return:
"""
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) < area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,
item) < area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_down(left_point, max_point, lists, border_points)
border_point_down(max_point, right_point, lists, border_points)
def order_border(lists):
"""
返回顺时针的边界点集
:param lists: 无序边界点集
:return: list [( , )...( , )]
"""
lists.sort()
first_x, first_y = lists[0] # 最左边的点
last_x, last_y = lists[-1] # 最右边的点
list_border_up = [] # 上半边界
for item in lists:
x, y = item
if y > max(first_y, last_y):
list_border_up.append(item)
if min(first_y, last_y) < y < max(first_y, last_y):
if calc_area(lists[0], lists[-1], item) > 0:
list_border_up.append(item)
else:
continue
list_border_down = [_ for _ in lists if _ not in list_border_up] # 下半边界
list_end = list_border_up + list_border_down[::-1] # 最终顺时针输出的边界点
return list_end
def draw(list_points, list_borders):
"""
画图
:param list_points: 所有点集
:param list_borders: 所有边界点集
:return: picture
"""
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
list_borders.append(list_borders[0])
for i in range(len(list_borders) - 1):
one_, oneI = list_borders[i]
two_, twoI = list_borders[i + 1]
plt.plot([one_, two_], [oneI, twoI])
plt.scatter(list_all_x, list_all_y)
plt.show()
def main():
"""
:return: 所有点
"""
inputs = list(map(int, input().split()))
if len(inputs) == 1:
return rand_point_set(inputs[0])
elif len(inputs) == 2:
return rand_point_set(inputs[0], inputs[1])
elif len(inputs) == 3:
return rand_point_set(inputs[0], inputs[1], inputs[2])
else:
print("\033[31m输入数据太多,请重新输入!\033[0m")
main()
if __name__ == "__main__":
print("""输入规则:
最少一个最多三个
后面可以跟数字用来指定生成区间(默认[0,100]),中间用空格隔开
例如:
输入 10 ---即为在默认区间[0,100]生成10个随机点
输入 10 50 ---即为在区间[50,100]生成10个随机点
输入 10 50 200 ---即为在区间[50,200]生成10个随机点
请输入:\t""")
list_points = main() # 所有点
list_points.sort()
border_points = [] # 边界点集
border_point_up(list_points[0], list_points[-1], list_points, border_points) # 上边界点集
border_point_down(list_points[0], list_points[-1], list_points, border_points) # 下边界点集
border_points.append(list_points[0])
border_points.append(list_points[-1]) # 将首尾两个点添加到边界点集中
# print(order_border(border_points)) # 顺时针输出边界点
draw(list_points, order_border(border_points))
下面是我用pyecharts画的蛮力法和分治法实间对比图:
可以看到,蛮力法随着点数的增加,时间几乎是呈指数型增长;而分治法相对来说增长比较平缓,甚至于100个点的蛮力法耗时比100000个点的分治法耗时还要多!
PS:
上面的曲线是根据我的蛮力法和分治法得出来的数据,而且由于电脑配置、网速、后台程序等等一系列原因,不同的设备结果可能会略有差异。但是总体增长趋势趋势一定是不变的!
🆗
最后在放一张分治法100000个点的图:(也没啥看头…🌚)