恒定时间随机选择和删除

时间:2013-02-18 20:53:53

标签: python data-structures graph

我正在尝试用Python实现MultiGraph的边缘列表。

到目前为止我尝试了什么:

>>> l1 = Counter({(1, 2): 2, (1, 3): 1})
>>> l2 = [(1, 2), (1, 2), (1, 3)]

l1具有两个顶点之间所有边缘的恒定时间删除(例如del l1[(1, 2)]),但在这些边缘上的线性时间随机选择(例如random.choice(list(l1.elements())))。请注意,您必须选择elements(与l1本身相比)。

l2具有恒定时间随机选择(random.choice(l2)),但所有元素的线性时间删除等于给定边缘([i for i in l2 if i != (1, 2)])。

问题:是否有一个Python数据结构可以为我提供恒定时间随机选择和删除?

1 个答案:

答案 0 :(得分:2)

我不认为你想要做的事情在理论上是可以实现的。

如果您使用加权值来表示重复项,则无法获得恒定时间随机选择。您可能做的最好的是某种跳过列表类型的结构,它允许您通过加权索引进行二进制搜索,这是对数的。

如果你使用加权值来表示重复项,那么你需要一些允许你存储多个副本的结构。并且哈希表不会这样做 - 重复必须是独立的对象(例如,(edge, autoincrement)),这意味着没有办法在常量时间内删除所有匹配某些标准的内容。

如果你能接受对数时间,那么显而易见的选择是一棵树。例如,使用blist

>>> l3 = blist.sortedlist(l2)

随机选择一个:

>>> edge = random.choice(l3)

The documentation似乎并不能保证这不会做O(n)。但幸运的是,3.32.7的来源表明它会做正确的事情。如果您不相信,只需写下l3[random.randrange(len(l3))]

要删除边的所有副本,您可以这样做:

>>> del l3[l3.bisect_left(edge):l3.bisect_right(edge)]

或者:

>>> try:
...     while True:
...         l3.remove(edge)
... except ValueError:
...     pass

文档说明了所涉及的每项操作的确切性能保证。特别是,len是常量,而索引或切片的索引,切片,删除,二等分和按值删除都是对数的,因此两个操作都以对数结束。

(值得注意的是blist是一个B +树;你可能会从红黑树或treap或其他东西中获得更好的性能。你可以找到大多数数据结构的良好实现的PyPI。)


正如senderle所指出的,如果边缘的最大副本数远小于集合的大小,则可以创建一个数据结构,该数据结构在最大副本数量上按时间间隔进行。将他的建议翻译成代码:

class MGraph(object):
    def __init__(self):
        self.edgelist = []
        self.edgedict = defaultdict(list)
    def add(self, edge):
        self.edgedict[edge].append(len(self.edgelist))
        self.edgelist.append(edge)
    def remove(self, edge):
        for index in self.edgedict.get(edge, []):
            maxedge = len(self.edgelist) - 1
            lastedge = self.edgelist[maxedge]
            self.edgelist[index], self.edgelist[maxedge] = self.edgelist[maxedge], self.edgelist[index]
            self.edgedict[lastedge] = [i if i != maxedge else index for i in self.edgedict[lastedge]]
            del self.edgelist[-1]
        del self.edgedict[edge]
    def choice(self):
        return random.choice(self.edgelist)

(当然,您可以使用三线查找并更新就地更改replace-list-with-list-comprehension行,但这仍然是重复数量的线性。)

显然,如果你打算真正使用它,你可能想要加强课程。通过实施一些方法,您可以使其看起来像list个边缘,每个边缘的多个副本的settupleCounter等然后让适当的collections.abc.Foo / collections.Foo填补其余部分。


那么哪个更好?那么,在你的示例中,平均重复数是列表大小的一半,最大值是大小的2/3。如果对于您的真实数据来说这是真的,那么树会更好,因为log N显然会吹走(N/2)**2。另一方面,如果重复很少,那么senderle的解决方案显然会更好,因为如果W**2为1,W仍为1。

当然,对于3元素样本,恒定开销和乘数将主导一切。但据推测,你真正的收藏并不是那么小。 (如果是,只需使用list ...)

如果您不知道如何表征您的真实数据,请编写两个实现并使用各种实际输入对它们进行计时。