我的直觉告诉我没有好办法实现这一目标,但是,与斯蒂芬科尔伯特先生不同,我宁愿相信一个开发者社区而不是我的直觉......
有没有一种已知的方法可以有效地实现“两个世界中最好的”列表,一个通过索引和 O(1)插入/删除(如链接列表)提供随机访问的列表?
我预见到两种可能的结果:要么“不,这是不可能的,出于以下明显的原因......”或“呃,是的,已经完成了;请看这里,这里和这里。”
答案 0 :(得分:14)
我认为插入和查找都不可能获得O(1)
。添加数组的时刻(甚至是花哨的可拆分向量),插入变为O(n)
。
根据列表的预期行为,有一些方法可以减轻损害。如果将有比插入/删除更多的查找,那么使用向量(可变大小的数组)可能更好 - 这些是合理有效的,不像数组,但比遍历列表更好(因为这些往往是列表对于数组而言,它仍然在技术上遍历列表,但列表中的每个元素通常都有其大小,这使得它更有效。)
如果插入和删除更频繁,您可以使索引构建为惰性索引,以便仅在需要时才进行。例如,插入和删除只会更改链接列表部分(并将索引标记为脏) - 只有当有人尝试使用索引时,才会重建并标记为干净。
您甚至可以通过保留第一个脏条目的记录来优化重建。这意味着如果您只在列表的后半部分插入或删除,则当有人想要使用它时,您无需重建整个索引。
我曾经实施过的解决方案是2D List。通过这个,我的意思是:
+-----------+ +-----------+ +-----------+
List -> | count = 7 | -> | Count = 4 | -> | Count = 6 | -> null
+-----------+ +-----------+ +-----------+
| | |
V V V
+-----------+ +-----------+ +-----------+
| [0] | | [7] | | [11] |
+-----------+ +-----------+ +-----------+
| | |
V V V
+-----------+ +-----------+ +-----------+
| [1] | | [8] | | [12] |
+-----------+ +-----------+ +-----------+
| | |
: : :
: : :
| | |
V V V
+-----------+ +-----------+ +-----------+
| [6] | | [10] | | [16] |
+-----------+ +-----------+ +-----------+
| | |
V V V
null null null
虽然这样做了插入和查找O(n),但余额是正确的。在纯数组解决方案中,查找为O(1)
,插入为O(n)
。对于纯链接列表,插入为O(1)
(一旦找到插入点,当然,操作本身为O(n)
),查找为O(n)
。
对于两者而言,2D列表为O(n)
但具有较低的因子。如果您要插入,只需检查每列的第一行即可找到正确的列。然后你遍历列本身寻找正确的行。然后插入该项目并增加该列的计数。类似地,对于删除,尽管在这种情况下计数减少,并且当计数达到零时整个列被删除。
对于索引查找,您遍历列以查找正确的列,然后遍历列中的项以获取正确的项。
而且,它甚至可以通过尝试保持最大高度和宽度大致相同来自动调整。
答案 1 :(得分:4)
如果你认为O(log N) == O(1),
退房:
答案 2 :(得分:2)
当我在课程中实现链接列表时,我考虑通过存储3个附加字段来优化访问时间:列表中间的节点,最近访问的节点的索引和最近访问的节点本身。 / p>
要通过索引检索节点,我将首先查看到达给定索引处节点的所有可用路径,然后选择最便宜的方式来执行此操作。方法很简单:
我们所需指数和起始指数差异最小的路径将是最便宜的选择。如果尚未访问任何节点,则可以将最近访问的节点设置为中间节点。当然,由于偶数个元素没有实际的中间,所以我只选择n / 2的底线。
无论如何,我从来没有真正实现这种优化,甚至真的分析它,但我希望我能提供帮助。
答案 3 :(得分:1)
你的直觉是正确的。
链接列表是O(1)插入/删除,因为您执行的插入或删除操作的操作只是切换几个指针(您插入的对象上的一个或两个,以及一个或两个上的一个或两个)其他对象)。显然,这不会因列表的大小而改变。
跳过列表将为您提供O(logn)查找,但由于您正在维护索引,因此也意味着O(logn)插入/删除,因为该索引需要保持最新。
您需要维护用于查找的任何并行数据结构,因此您的复杂性将根据该索引的复杂性进行扩展。
你想解决一个特殊的问题吗?
例如,如果可以保证完美的哈希,则可以进行O(n)插入,删除和查找。但是,您需要提前了解有关数据的一些信息。
答案 4 :(得分:1)
哈希表怎么样?您可以通过密钥和O(1)插入/删除来获得O(1)随机访问。问题是条目是无序的。
要有效实施有序序列,请查看finger trees。它们允许O(1)访问head
和last
以及O(log n)对内部节点的随机访问。在O(1)中的任意一端插入或删除。值得注意的是,手指树的反转需要不变的时间。
答案 5 :(得分:0)
我不知道插入时的确切BigO(因为这会因样本大小和增长而有所不同),但Java java.util.LinkedList
会立即浮现在脑海中。
http://java.sun.com/j2se/1.5.0/docs/api/java/util/LinkedList.html
编辑:是的,显然在它下面仍然是一个真正的链表,因此索引得到的可能是O(n / 2),这当然是正式的O(n)。你总是可以浪费一大堆空间并实现一个List实现,它可以保持并行链表和数组延迟插入/删除。
答案 6 :(得分:0)
Java LinkedList
对索引获取具有O(n)访问权限。 LinkedList
扩展AbstractSequentialList
以表明它不提供O(1)索引获取。
我建议你看看Apache's TreeList。它提供O(log n)插入/删除和O(1)索引查找。
答案 7 :(得分:0)
虽然我不认为您可以获得整数索引,但如果您使用“引用”类型,则可以使用支持哈希表。