给定凸包中的n个点的顺时针排序列表,查找最小面积包围矩形是一个O(n)操作。(对于凸包查找,可以在O(n log n)时间内查找,请参见
activestate.com recipe 66527或查看非常紧凑的
tixxit.net上Graham扫描代码。)
下面的Python程序使用类似于计算凸多边形最大直径的通常O(n)算法的技术。也就是说,它维护三个索引(iL、iP、iR),相对于给定基线,分别指向最左侧、对面和最右侧的点。每个索引最多经过n个点。程序的示例输出如下所示(附加标题):
i iL iP iR Area
0 6 8 0 203.000
1 6 8 0 211.875
2 6 8 0 205.800
3 6 10 0 206.250
4 7 12 0 190.362
5 8 0 1 203.000
6 10 0 4 201.385
7 0 1 6 203.000
8 0 3 6 205.827
9 0 3 6 205.640
10 0 4 7 187.451
11 0 4 7 189.750
12 1 6 8 203.000
例如,i=10的条目表示相对于从点10到11的基准线,点0最左,点4相对,点7最右,产生一个面积为187.451单位。
请注意,代码使用mostfar()来推进每个索引。 mostfar()的mx,my参数告诉它要测试哪个极端值;例如,使用mx,my = -1,0,mostfar()将尝试最大化-rx(其中rx是点的旋转x),从而找到最左边的点。 请注意,在不精确计算时,if mx * rx + my * ry >= best应该使用epsilon允许:当外壳有众多点时,四舍五入误差可能会成为问题,并导致该方法不正确地不推进索引。
下面显示代码。 外壳数据取自上面的问题,省略了不相关的大偏移和相同的小数位数。
import math
hull = [(23.45, 57.39), (23.45, 60.39), (24.45, 63.39),
(26.95, 68.39), (28.45, 69.89), (34.95, 71.89),
(36.45, 71.89), (37.45, 70.39), (37.45, 64.89),
(36.45, 63.39), (34.95, 61.39), (26.95, 57.89),
(25.45, 57.39), (23.45, 57.39)]
def mostfar(j, n, s, c, mx, my):
xn, yn = hull[j][0], hull[j][1]
rx, ry = xn*c - yn*s, xn*s + yn*c
best = mx*rx + my*ry
while True:
x, y = rx, ry
xn, yn = hull[(j+1)%n][0], hull[(j+1)%n][1]
rx, ry = xn*c - yn*s, xn*s + yn*c
if mx*rx + my*ry >= best:
j = (j+1)%n
best = mx*rx + my*ry
else:
return (x, y, j)
n = len(hull)
iL = iR = iP = 1
pi = 4*math.atan(1)
for i in range(n-1):
dx = hull[i+1][0] - hull[i][0]
dy = hull[i+1][1] - hull[i][1]
theta = pi-math.atan2(dy, dx)
s, c = math.sin(theta), math.cos(theta)
yC = hull[i][0]*s + hull[i][1]*c
xP, yP, iP = mostfar(iP, n, s, c, 0, 1)
if i==0: iR = iP
xR, yR, iR = mostfar(iR, n, s, c, 1, 0)
xL, yL, iL = mostfar(iL, n, s, c, -1, 0)
area = (yP-yC)*(xR-xL)
print ' {:2d} {:2d} {:2d} {:2d} {:9.3f}'.format(i, iL, iP, iR, area)
注意:要获取最小面积包围矩形的长度和宽度,请按以下方式修改上述代码。这将生成一个输出行,如下所示。
Min rectangle: 187.451 18.037 10.393 10 0 4 7
其中第二个和第三个数字表示矩形的长度和宽度,四个整数给出了位于其边上的点的索引号。
minRect = (1e33, 0, 0, 0, 0, 0, 0)
if area < minRect[0]:
minRect = (area, xR-xL, yP-yC, i, iL, iP, iR)
print 'Min rectangle:', minRect
print 'Min rectangle: ',
for x in ['{:3d} '.format(x) if isinstance(x, int) else '{:7.3f} '.format(x) for x in minRect]:
print x,
print