在计算天际线面积时如何减少/优化内存使用?

时间:2017-11-14 19:28:31

标签: python algorithm

我试图计算天际线的面积(具有相同基线的重叠矩形)

building_count = int(input())
items = {} # dictionary, location on x axis is the key, height is the value
count = 0 # total area
for j in range(building_count):
    line = input().split(' ')
    H = int(line[0]) # height
    L = int(line[1]) # left point (start of the building)
    R = int(line[2]) # right point (end of the building)
    for k in range(R - L):
        if not (L+k in  items): # if it's not there, add it
            items[L+k] = H
        elif H > items[L+k]: # if we have a higher building on that index
            items[L+k] = H
for value in items.values(): # we add each column basically
    count += value
print(count)

样本输入将是:

5
3 -3 0
2 -1 1
4 2 4
2 3 7
3 6 8

,输出为29

问题是内存效率,当有很多值时,脚本会抛出MemoryError。任何人都有一些优化内存使用的想法?

2 个答案:

答案 0 :(得分:3)

您要为范围中的每个整数值分配一个单独的键值对。想象一下R = 1L = 100000的情况。您的items字典将填充1000000个项目。你处理/删除重叠的基本想法是合理的,但你做的方式是大量的过度杀伤。

就像生活中的其他许多事情一样,这是一个伪装的图形问题。将顶点成像为您尝试处理的矩形,并且(加权)边缘是重叠。复杂的是,你不能只是添加顶点的区域并减去重叠的区域,因为许多重叠也相互重叠。可以通过应用将两个重叠矩形转换为非重叠矩形的变换来解决重叠问题,从而有效地切割连接它们的边缘。转换如下图所示。请注意,在某些情况下,其中一个顶点也会被删除,从而简化了图形,而在另一种情况下,会添加一个新的顶点:

enter image description here 绿色:重叠被切断。

通常情况下,如果我们之间有m个矩形和n重叠,构建图形将是O(m2)操作,因为我们必须检查所有顶点是否相互重叠。但是,我们可以完全绕过输入图的构造以获得O(m + n)遍历算法,这将是最优的,因为我们将只分析每个矩形一次,并且尽可能有效地构造没有重叠的输出图。 O(m + n)假定您的输入矩形按其左边缘按升序排序。如果不是这种情况,则算法将O(mlog(m) + n)考虑初始排序步骤。请注意,随着图密度的增加,n将从~m变为~m2。这证实了一个直观的想法,即重叠次数越少,您希望过程在O(m)时间内运行的次数越多,而且重叠次数越多,您就越接近O(m2)时间。< / p>

提出的算法的空间复杂度为O(m):输入中的每个矩形将导致输出中最多两个矩形,2m = O(m)

足够复杂性分析和算法本身。输入将是由LRH定义的一系列矩形。我将假设输入按最左边L排序。输出图形将是由相同参数定义的矩形的链接列表,由最右侧边缘按降序顺序排序。列表的头部将是最右边的矩形。任何矩形之间的输出都没有重叠,因此天际线的总面积将只是每个H * (R - L)输出矩形的~m的总和。

选择链表的原因是我们需要的唯一两个操作是从头节点迭代,最便宜的插入可以按排序顺序维护列表。排序将作为重叠检查的一部分完成,因此我们不需要通过列表或类似的任何类型的二进制搜索。

由于输入列表是通过增加左边缘来排序的,并且输出列表是通过减小右边缘来排序的,因此我们可以保证添加的每个矩形仅针对它实际上与 1 重叠的矩形进行检查。我们将进行重叠检查和移除,如上图所示,直到我们到达一个左边缘小于或等于新矩形左边缘的矩形。保证输出列表中的所有其他矩形不与新矩形重叠。这种检查和斩波操作保证每次重叠最多访问一次,并且不会不必要地处理非重叠矩形,使算法达到最佳。

在我展示代码之前,这是一个运行中的算法图。红色矩形是新的矩形;请注意,它们的左边缘向右移动。蓝色矩形是已添加的并与新矩形重叠的矩形。黑色矩形已添加,并且与新的矩形没有重叠。编号表示输出列表的顺序。它总是从右边完成。链表是保持这种进展的完美结构,因为它允许廉价的插入和替换:

enter image description here

这是算法的一种实现,它假定输入坐标作为具有属性lrh的可迭代对象传入。假设迭代顺序按左边缘排序。如果不是这种情况,请先将sortedlist.sort应用于输入:

from collections import namedtuple

# Defined in this order so you can sort a list by left edge without a custom key
Rect = namedtuple('Rect', ['l', 'r', 'h'])

class LinkedList:
    __slots__ = ['value', 'next']

    """
    Implements a singly-linked list with mutable nodes and an iterator.
    """
    def __init__(self, value=None, next=None):
        self.value = value
        self.next = next

    def __iter__(self):
        """
        Iterate over the *nodes* in the list, starting with this one.

        The `value` and `next` attribute of any node may be modified
        during iteration.
        """
        while self:
            yield self
            self = self.next

    def __str__(self):
        """
        Provided for inspection purposes.

        Works well with `namedtuple` values.
        """
        return ' -> '.join(repr(x.value) for x in self)


def process_skyline(skyline):
    """
    Turns an iterable of rectangles sharing a common baseline into a
    `LinkedList` of rectangles containing no overlaps.

    The input is assumed to be sorted in ascending order by left edge.
    Each element of the input must have the attributes `l`, r`, `h`.

    The output will be sorted in descending order by right edge.

    Return `None` if the input is empty.
    """
    def intersect(r1, r2, default=None):
        """
        Return (1) a flag indicating the order of `r1` and `r2`,
        (2) a linked list of between one and three non-overlapping
        rectangles covering the exact same area as `r1` and `r2`,
        and (3) a pointer to the last nodes (4) a pointer to the
        second-to-last node, or `default` if there is only one node.

        The flag is set to True if the left edge of `r2` is strictly less
        than the left edge of `r1`. That would indicate that the left-most
        (last) chunk of the tuple came from `r2` instead of `r1`. For the
        algorithm as a whole, that means that we need to keep checking for
        overlaps.

        The resulting list is always returned sorted descending by the
        right edge. The input rectangles will not be modified. If they are
        not returned as-is, a `Rect` object will be used instead.
        """
        # Swap so left edge of r1 < left edge of r2
        if r1.l > r2.l:
            r1, r2 = r2, r1
            swapped = True
        else:
            swapped = False

        if r2.l >= r1.r:
            # case 0: no overlap at all
            last = LinkedList(r1)
            s2l = result = LinkedList(r2, last)
        elif r1.r < r2.r:
            # case 1: simple overlap
            if r1.h > r2.h:
                # Chop r2
                r2 = Rect(r1.r, r2.r, r2.h)
            else:
                r1 = Rect(r1.l, r2.l, r1.h)
            last = LinkedList(r1)
            s2l = result = LinkedList(r2, last)
        elif r1.h < r2.h:
            # case 2: split into 3
            r1a = Rect(r1.l, r2.l, r1.h)
            r1b = Rect(r2.r, r1.r, r1.h)
            last = LinkedList(r1a)
            s2l = LinkedList(r2, last)
            result = LinkedList(r1b, s2l)
        else:
            # case 3: complete containment
            result = LinkedList(r1)
            last = result
            s2l = default

        return swapped, result, last, s2l

    root = LinkedList()

    skyline = iter(skyline)
    try:
        # Add the first node as-is
        root.next = LinkedList(next(skyline))
    except StopIteration:
        # Empty input iterator
        return None

    for new_rect in skyline:
        prev = root
        for rect in root.next:
            need_to_continue, replacement, last, second2last = \
                    intersect(rect.value, new_rect, prev)
            # Replace the rectangle with the de-overlapped regions
            prev.next = replacement
            if not need_to_continue:
                # Retain the remainder of the list
                last.next = rect.next
                break
            # Force the iterator to move on to the last node
            new_rect = last.value
            prev = second2last

    return root.next

现在计算总面积是微不足道的:

skyline = [
    Rect(-3, 0, 3), Rect(-1, 1, 2), Rect(2, 4, 4),
    Rect(3, 7, 2), Rect(6, 8, 3),
]
processed = process_skyline(skyline)
area = sum((x.value.r - x.value.l) * x.value.h for x in processed) if processed else None

注意输入参数的更改顺序(h移动到结尾)。生成的area29。这与我手工计算得到的结果相匹配。你也可以

>>> print(processed)
Rect(l=6, r=8, h=3) -> Rect(l=4, r=6, h=2) -> Rect(l=2, r=4, h=4) ->
Rect(l=0, r=1, h=2) -> Rect(l=-3, r=0, h=3)

这可以从下面显示的输入/输出图中预期到:

enter image description here

作为额外验证,我在列表的开头添加了一个新建筑Rect(-4, 9, 1)。它与所有其他内容重叠,并向area添加三个单位,或32的最终结果。 processed出现为:

Rect(l=8, r=9, h=1) -> Rect(l=6, r=8, h=3) -> Rect(l=4, r=6, h=2) ->
Rect(l=2, r=4, h=4) -> Rect(l=1, r=2, h=1) -> Rect(l=0, r=1, h=2) ->
Rect(l=-3, r=0, h=3) -> Rect(l=-4, r=-3, h=1)

注意:

虽然我确信这个问题已经解决了很多次,但我在这里提出的解决方案完全是我自己的工作,没有咨询任何其他参考资料。使用隐式图形表示和结果分析的想法受到最近阅读Steven Skiena的算法设计手册第二版的启发。这是我见过的最好的综合性书籍之一。

1 从技术上讲,如果一个新的矩形与任何其他矩形不重叠,它将与一个不重叠的矩形进行核对。如果总是这样额外检查,算法将进行额外的m - 1比较。幸运的是,m + m + n - 1 = O(m + n)即使我们总是也必须检查一个额外的矩形(我们不会这样做)。

答案 1 :(得分:2)

获取MemoryError的原因是正在创建的字典的大小。在最坏的情况下,dict可以有10 ^ 10个键,最终会占用你所有的内存。如果真的有需要,shelve是一种可能的解决方案,可以使用这么大的字典。

假设有一个10 0 100的建筑物和另一个20 50 150的建筑物,那么该列表可能包含[(-10^9, 0), (0, 10), (50, 20), (150, 0), (10^9, 0)]等信息。当您遇到更多建筑物时,可以在此列表中添加更多条目。这将是O(n^2)

This可能会对您有所帮助。