解决方案
在图中所有线的空间中,经过一个角的线正是那些即将减少数量或交点的线。换句话说,它们各自形成一个局部最大值。
对于每条至少经过一个角的线,存在一条经过两个具有相同交叉点数的角的关联线。
结论是我们只需要检查由两个矩形角形成的线,因为它们形成了一个完全代表我们问题的局部最大值的集合。从那些我们选择交叉点最多的那个。
时间复杂度
此解决方案首先需要恢复通过两个角的所有线。这样的行数是O(n^2)。
然后我们需要计算给定直线和矩形之间的交叉点数。这显然可以通过比较每个矩形在 O(n) 中完成。
可能有更有效的方法来进行,但我们知道这个算法最多O(n^3)。
Python3 实现
这是该算法的 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