我面临的一个应用程序,我必须设计一个具有随机访问权限(或至少优于O(n))的容器具有廉价(O(1))插入和删除,并根据插入时指定的顺序(等级)。
例如,如果我有以下数组:
[2, 9, 10, 3, 4, 6]
我可以在索引2上调用删除以删除10 ,我也可以通过插入13 来调用索引1上的插入。
在我完成这两项操作之后:
[2, 13, 9, 3, 4, 6]
数字存储在序列中,插入/删除操作需要索引参数来指定插入数字的位置或应删除的数字。
我的问题是,除了链接列表和向量之外,什么样的数据结构可以维护这样的东西?我倾向于优先考虑下一个可用索引的堆。但我一直看到一些关于融合树有用的东西(但在理论意义上更多)。
什么样的数据结构可以在保持内存消耗的同时为我提供最佳的运行时间?我一直在玩一个保留哈希表的插入顺序,但到目前为止它还没有成功。
我直接使用std :: vector折腾的原因是因为我必须根据这些基本操作构造一些预先形成向量的东西。 容器的大小有可能增长到数十万个元素,因此承诺在std :: vector中进行转换是不可能的。带有链接列表的相同问题行(即使是双重链接),将其遍历给定索引将采用最坏情况O(n / 2),其舍入为O(n)。
我在考虑一个包含Head,Tail和Middle指针的双重链表,但我觉得它不会好多了。
答案 0 :(得分:7)
在基本用法中,为了能够在任意位置插入和删除,您可以使用链接列表。它们允许插入/移除O(1),但前提是您已经在列表中找到了要插入的位置。您可以插入“在给定元素之后”(即,给定指向元素的指针),但是您无法有效地插入“在给定索引处”。
为了能够在给定索引的情况下插入和删除元素,您将需要更高级的数据结构。至少存在两种我所知道的结构。
一个是rope结构,可以在某些C ++扩展(SGI STL中使用,或通过#include <ext/rope>
在GCC中使用)。它允许在任意位置插入/移除O(log N)。
允许O(log N)插入/删除的另一个结构是隐式treap(也就是隐式笛卡尔树),您可以在http://codeforces.com/blog/entry/3767,Treap with implicit keys或https://codereview.stackexchange.com/questions/70456/treap-with-implicit-keys找到一些信息。
也可以修改隐式treap以允许在其中找到最小值(并且还支持更多操作)。不确定绳索是否可以处理这个问题。
UPD :事实上,我猜您可以通过将其转换为“隐式密钥”来为您的请求调整任何O(log N)二叉搜索树(例如AVL或红黑树) “计划。概述如下。
想象一个二叉搜索树,它在每个给定时刻存储连续数字1,2,...,N作为其键(N是树中节点的数量)。每次我们更改树(插入或删除节点)时,我们重新计算所有存储的键,使它们仍然从1到N的新值。这将允许在任意位置插入/移除,因为键现在是位置,但是所有密钥更新都需要太多时间。
为避免这种情况,我们不会明确地在树中存储密钥。相反,对于每个节点,我们将在其子树中存储节点数。因此,无论何时我们从树根向下,我们都可以跟踪当前节点的索引(位置) - 我们只需要将左侧的子树大小相加。这允许我们,给定 k ,在O上找到具有索引 k 的节点(即,二进制搜索树的标准顺序中的第k个) (记录N)时间。在此之后,我们可以使用标准二叉树程序在该位置执行插入或删除;我们只需更新更新期间更改的所有节点的子树大小,但这可以在每个节点更改的O(1)时间内轻松完成,因此总插入或删除时间将为O(log N),如原始的二叉搜索树。
因此,这种方法允许使用任何O(log N)二分搜索树作为基础,在O(log N)时间内在给定位置插入/移除/访问节点。您当然可以在节点中存储所需的附加信息(“值”),甚至可以通过保持每个节点的子树的最小值来计算树中这些值的最小值。
然而,前面提到的treap和rope更先进,因为它们也允许分割和合并操作(采用子串/子阵列并连接两个字符串/数组)。
答案 1 :(得分:0)
考虑一个跳过列表,它可以在"indexable"变体中实现线性时间等级操作。
对于算法(伪代码),请参阅A Skip List Cookbook, by Pugh。
上面的@Petr概述的“隐式密钥”二叉搜索树方法可能更容易获得,甚至可能表现得更好。