我不相信插入和查找都可以获得O(1)。添加数组(甚至是花哨的可拆分向量)的那一刻,插入变为O(n)。
根据列表的预期行为,有多种方法可以减轻损害。如果查找比插入/删除更多,最好只使用向量(可变大小的数组) - 这些是相当有效的,不太像数组,但比遍历列表更好(因为这些往往是列表数组,它在技术上仍然是遍历一个列表,但列表中的每个元素通常都有自己的大小,这使得它更有效)。
如果插入和删除更频繁,您可以使索引构建一个惰性索引,以便仅在需要时完成。例如,插入和删除只会改变链表部分(并将索引标记为脏)——只有当有人尝试使用索引时,它才会被重建并标记为干净。
您甚至可以通过记录第一个脏条目来优化重建。这意味着如果您只在列表的后半部分插入或删除,当有人要使用它时,您不需要重建整个索引。
我曾经实施的一个解决方案是 2D 列表。我的意思是:
+-----------+ +-----------+ +-----------+
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)。
两者的二维列表都是O(n),但系数较低。如果您要插入,只需检查每列的第一行即可找到正确的列。然后你遍历列本身寻找正确的行。然后插入该项目并增加该列的计数。删除也是类似的,但在这种情况下,计数会减少,当计数为零时,整个列都会被删除。
对于索引查找,您遍历列以找到正确的列,然后遍历列中的项以获取正确的项。
而且,它甚至可以通过尝试保持最大高度和宽度大致相同来自动调整。