支持O(1)随机访问和最坏情况O(1)的数据结构是否附加?

时间:2011-01-29 01:21:52

标签: arrays performance data-structures language-agnostic big-o

我意识到一个可调整大小的索引集合,它使用一个数组来存储它的元素(如.NET中的List<T>或Java中的ArrayList)在集合的末尾有amortized O(1) insertion time。但是,在关键时刻总是存在一个讨厌的插入,其中集合只是达到了它的容量,下一次插入需要内部数组中所有元素的完整副本到新的(可能是两倍大)。

一个常见的错误(在我看来)是使用链表来“修复”这个问题;但是我认为为每个元素分配一个节点的开销可能非常浪费,事实上,在极少数情况下插入成本很高的情况下,保证O(1)插入的好处相形见绌 - 事实上,每个< em>其他数组插入明显更便宜(也更快)。

我认为可能有意义的是一种混合方法,它由一个链接的数组列表组成,每当当前的“head”数组达到其容量时,就会在链表中添加两倍大的新数组。然后,由于链表仍然具有原始数组,因此不需要复制。从本质上讲,这似乎与List<T>ArrayList方法类似(对我而言),除非您以前曾经承担过复制内部数组所有元素的成本,这里只会产生成本分配 new 数组加上单个节点插入。

当然,如果需要它们会使其他功能复杂化(例如,插入/移出集合的中间);但正如我在标题中所表达的那样,我真的只是在寻找仅添加(和迭代)集合。

是否有适合此目的的数据结构?或者,你能想到一个吗?

4 个答案:

答案 0 :(得分:59)

有一个称为可扩展数组的漂亮结构,它具有最坏情况的O(1)插入和O(n)内存开销(也就是说,它与动态数组渐近可比,但有O(1)最坏情况插入)。诀窍是采用向量使用的方法 - 加倍和复制 - 但是使复制变得懒惰。例如,假设您有一个包含四个元素的数组,如下所示:

[1] [2] [3] [4]

如果你想添加一个新数字,比如说5,你首先要分配一个两倍大的数组:

[1] [2] [3] [4]
[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

接下来,将5插入新数组:

[1] [2] [3] [4]
[ ] [ ] [ ] [ ] [5] [ ] [ ] [ ]

最后,将旧数组中的4下拉到新数组中:

[1] [2] [3] [ ]
[ ] [ ] [ ] [4] [5] [ ] [ ] [ ]

从现在开始,每次执行插入操作时,都要将元素添加到新数组中,然后从旧数组中再拉下一个元素。例如,在添加6之后,我们将获得

[1] [2] [ ] [ ]
[ ] [ ] [3] [4] [5] [6] [ ] [ ]

在插入两个以上的值之后,我们最终在这里:

[ ] [ ] [ ] [ ]
[1] [2] [3] [4] [5] [6] [7] [8]

如果我们现在需要再添加一个元素,我们将丢弃现在为空的旧数组并分配一个比当前数组大两倍的数组(能够容纳16个元素):

[1] [2] [3] [4] [5] [6] [7] [8]
[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

重复这个过程。折扣内存分配的成本(通常是数组大小的次线性),每次插入最多可以完成O(1)工作。

查找仍然是O(1),因为你只是决定要查看两个数组中的哪一个,而中间的插入是因为改组而是O(n)。

如果你很好奇,我有a Java implementation of this structure on my personal site.我不知道你会发现它有多么有用,但我们非常欢迎你试一试。

希望这有帮助!

编辑 :如果您想花一点时间阅读研究论文并尝试实施相当复杂的数据结构,您可以获得相同的结果( O(√n)空间开销中的最坏情况O(1)追加)(顺便说一句,这可能是最优的)使用the ideas in this paper.我从来没有实际实现过这个,但它确实非常值得阅读如果记忆是一种超级稀缺的资源。有趣的是,它将上述结构用作子程序!

答案 1 :(得分:4)

当我需要这样的容器时,我使用"Resizeable Arrays in Optimal Time and Space"

中描述的结构的实现

答案 2 :(得分:1)

行。你所描述的几乎正是{+ 3}}在C ++标准库中的含义。不同之处在于,数组(通常)用于保存指向子数组的指针,而不是使用链接列表。

答案 3 :(得分:0)

一个想法是创建一些包含少量元素的列表,例如:

struct item
{
    int data[NUM_ITEMS];
    item *next;
}

在这种情况下,插入将采用O(1),如果达到限制,只需创建一个新块并将其附加到列表的末尾