我发现了这个挑战问题,其陈述如下:
假设在XY平面上有n个矩形。编写一个程序来计算可以用一条直线穿过的最大矩形数量。
我已经思考了相当长的时间,但是找不到任何解决方案。 也许在某个阶段,我们使用动态规划步骤,但是无法确定如何开始。
我发现了这个挑战问题,其陈述如下:
假设在XY平面上有n个矩形。编写一个程序来计算可以用一条直线穿过的最大矩形数量。
我已经思考了相当长的时间,但是找不到任何解决方案。 也许在某个阶段,我们使用动态规划步骤,但是无法确定如何开始。
在图中的所有线条中,那些穿过一个角落的线条恰好是交点数量即将减少的线条。换句话说,它们每个都形成了一个局部最大值。
对于每一条至少经过一个角的线,都存在一条关联线经过两个角,且具有相同数量的交点。
结论是,我们只需要检查由两个矩形角落组成的线条,因为它们组成了完全代表问题的局部最大值集合。从这些线条中选择具有最多交点的线条。
此解决方案首先需要恢复所有经过两个角的线条。这样的线条数量为O(n^2)。
然后,我们需要计算给定线条与矩形之间的交点数。通过与每个矩形进行比较,这显然可以在O(n)中完成。
可能有更有效的方法来处理,但我们知道该算法最多为O(n^3)。
以下是此算法的 Python 实现。我将其定位更多地向可读性而非效率,但它确切地执行了上述定义。
def get_best_line(rectangles):
"""
Given a set of rectangles, return a line which intersects the most rectangles.
"""
# Recover all corners from all rectangles
corners = set()
for rectangle in rectangles:
corners |= set(rectangle.corners)
corners = list(corners)
# Recover all lines passing by two corners
lines = get_all_lines(corners)
# Return the one which has the highest number of intersections with rectangles
return max(
((line, count_intersections(rectangles, line)) for line in lines),
key=lambda x: x[1])
该实现使用以下辅助程序。
def get_all_lines(points):
"""
Return a generator providing all lines generated
by a combination of two points out of 'points'
"""
for i in range(len(points)):
for j in range(i, len(points)):
yield Line(points[i], points[j])
def count_intersections(rectangles, line):
"""
Return the number of intersections with rectangles
"""
count = 0
for rectangle in rectangles:
if line in rectangle:
count += 1
return count
以下是用作矩形和直线数据结构的类定义。
import itertools
from decimal import Decimal
class Rectangle:
def __init__(self, x_range, y_range):
"""
a rectangle is defined as a range in x and a range in y.
By example, the rectangle (0, 0), (0, 1), (1, 0), (1, 1) is given by
Rectangle((0, 1), (0, 1))
"""
self.x_range = sorted(x_range)
self.y_range = sorted(y_range)
def __contains__(self, line):
"""
Return whether 'line' intersects the rectangle.
To do so we check if the line intersects one of the diagonals of the rectangle
"""
c1, c2, c3, c4 = self.corners
x1 = line.intersect(Line(c1, c4))
x2 = line.intersect(Line(c2, c3))
if x1 is True or x2 is True \
or x1 is not None and self.x_range[0] <= x1 <= self.x_range[1] \
or x2 is not None and self.x_range[0] <= x2 <= self.x_range[1]:
return True
else:
return False
@property
def corners(self):
"""Return the corners of the rectangle sorted in dictionary order"""
return sorted(itertools.product(self.x_range, self.y_range))
class Line:
def __init__(self, point1, point2):
"""A line is defined by two points in the graph"""
x1, y1 = Decimal(point1[0]), Decimal(point1[1])
x2, y2 = Decimal(point2[0]), Decimal(point2[1])
self.point1 = (x1, y1)
self.point2 = (x2, y2)
def __str__(self):
"""Allows to print the equation of the line"""
if self.slope == float('inf'):
return "y = {}".format(self.point1[0])
else:
return "y = {} * x + {}".format(round(self.slope, 2), round(self.origin, 2))
@property
def slope(self):
"""Return the slope of the line, returning inf if it is a vertical line"""
x1, y1, x2, y2 = *self.point1, *self.point2
return (y2 - y1) / (x2 - x1) if x1 != x2 else float('inf')
@property
def origin(self):
"""Return the origin of the line, returning None if it is a vertical line"""
x, y = self.point1
return y - x * self.slope if self.slope != float('inf') else None
def intersect(self, other):
"""
Checks if two lines intersect.
Case where they intersect: return the x coordinate of the intersection
Case where they do not intersect: return None
Case where they are superposed: return True
"""
if self.slope == other.slope:
if self.origin != other.origin:
return None
else:
return True
elif self.slope == float('inf'):
return self.point1[0]
elif other.slope == float('inf'):
return other.point1[0]
elif self.slope == 0:
return other.slope * self.origin + other.origin
elif other.slope == 0:
return self.slope * other.origin + self.origin
else:
return (other.origin - self.origin) / (self.slope - other.slope)
这是上述代码的一个可行示例。
rectangles = [
Rectangle([0.5, 1], [0, 1]),
Rectangle([0, 1], [1, 2]),
Rectangle([0, 1], [2, 3]),
Rectangle([2, 4], [2, 3]),
]
# Which represents the following rectangles (not quite to scale)
#
# *
# *
#
# ** **
# ** **
#
# **
# **
print('{} with {} intersections'.format(*get_best_line(rectangles)))
# prints: y = 0.50 * x + -5.00 with 3 intersections
O(n^2 (log n + m))
的答案。你们觉得怎么样? - גלעד ברקןO(n^2)
算法的草图,它将 Gassa 的想法与 Evgeny Kluev 关于双线安排作为排序角度序列的参考相结合。O(1)
时间内分割边,并遍历我们在填充二维平面时创建的面。为简单起见,让我们只使用以下矩形的十二个角中的三个:9| (5,9)___(7,9)
8| | |
7| (4,6)| |
6| ___C | |
5| | | | |
4| |___| | |
3| ___ |___|(7,3)
2| | | B (5,3)
1|A|___|(1,1)
|_ _ _ _ _ _ _ _
1 2 3 4 5 6 7
根据以下转换,我们将三个点(角落)插入到对偶平面中:
point p => line p* as a*p_x - p_y
line l as ax + b => point l* as (a, -b)
A,B,C
。 首先我们输入A => y = x - 1
。由于到目前为止只有一个边,我们插入B => y = 5x - 3
,这将创建顶点(1/2,-1/2)
并分割我们的边缘。(此解决方案的一个优雅之处在于,对偶平面中的每个顶点(点)实际上都是通过矩形角落穿过的线段的对偶点。观察1 = 1/2*1 + 1/2
和3 = 1/2*5 + 1/2
,得到点(1,1)
和(5,3)
。)C => y = 4x - 6
后,我们现在寻找它将相交的最左侧面(可能是不完整的面)。由于我们必须尝试每个面,所以这个搜索需要O(n)
的时间。我们找到并创建顶点(-3,-18)
,分割了5x - 3
的下部边缘,并沿着边缘向上移动以在顶点(5/3,2/3)
处分割x - 1
的右半部分。每次插入都需要O(n)
的时间,因为我们必须先找到最左侧的面,然后遍历每个面以分割边缘并标记顶点(线段的交点)。(C*) 4x - 6
的线变为水平,并且您将看到B*
现在具有正斜率而A*
具有负斜率。)Pick A, lookup A*
=> x - 1.
Obtain the concatenated sequence by traversing the edges in O(n)
=> [(B*) 5x - 3, (C*) 4x - 6] ++ [No points left of A]
Initialise an empty counter array, count_array of length n-1
Initialise a pointer, ptr, to track rectangle corners passed in
the opposite direction of the current vector.
Iterate:
vertex (1/2, -1/2)
=> line y = 1/2x + 1/2 (AB)
perform rectangle-count-logic
if the slope is positive (1/2 is positive):
while the point at ptr is higher than the line:
perform rectangle-count-logic
else if the slope is negative:
while the point at ptr is lower than the line:
perform rectangle-count-logic
=> ptr passes through the rest of the points up to the corner
across from C, so intersection count is unchanged
vertex (5/3, 2/3)
=> line y = 5/3x - 2/3 (AC)
(5,9)
在经过AC (y = 5/3x - 2/3)
的直线之上,这意味着在这一点上,我们将会计算与最右边矩形的相交,并且还没有重置它的计数,在这条线上总共有3个矩形。for point B => B* => 5x - 3: [No points right of B] ++ [(C*) 4x - 6, (A*) x - 1]
for point C => C* => 4x - 6: [(B*) 5x - 3] ++ [(A*) x - 1]
(note that we start at -90 deg up to -270 deg)
RES = 0 // maximum number of intersections
CORNERS[] // all rectangles corners listed as (x, y) points
for A in CORNERS
for B in CORNERS // optimization: starting from corner next to A
RES = max(RES, CountIntersectionsWithLine(A.x, A.y, B.x, B.y))
return RES
A
的角落开始内部循环来避免重复计算同一条线。O(n^2 (log n + m))
的答案。你觉得呢? - גלעד ברקן4n
次迭代周期将创建一个新的角。对于每个矩形的四个角,我们将迭代每个其他矩形。我们要插入的是标记配对矩形最远角相对于当前固定角所创建的弧的角度。例如,在下面的示例中,对于固定下方矩形的角R
,当插入中间矩形的记录时,我们将插入标记从p2
到p1
相对于R
所创建的弧的角度(约为(37 deg, 58 deg)
)。然后当我们检查相对于R
的高矩形时,我们将插入标记从p4
到p3
相对于R
所创建的弧的角度的区间(约为(50 deg, 62 deg)
)。