特殊字典的最佳数据结构

时间:2011-09-23 11:14:37

标签: algorithm data-structures hash complexity-theory trie

哪种数据结构在计算复杂性方面最佳,以实现(key,val)项目的字典,这些项目必须支持以下命令:

  1. Insert(key) - 使用val = 1
  2. 附加项目(key,val)
  3. Increment(key) - 增加现有的值(key,val)
  4. Find(key) - 返回(key,val)
  5. 的val
  6. Select(part_of_key) - 如果strstr(key,part_of_key)!=NULL以相同类型的新词典的形式返回所有项目(key,val)的列表(如果可能的话,不分配新的内存);例如,如果字典是{(红色,3),(蓝色,4),(绿色,1)},则选择(重新)= {(红色,3),(绿色,1)}
  7. Max(i) - 返回所有项目中具有第i个最大值的项目;例如,如果字典是{(红色,3),(蓝色,4),(绿色,1)},则Max(1)=蓝色,Max(2)=红色,Max(3)=绿色
  8. 键是字符串,值是正整数。字典中的项目数量预计会非常大。

    我认为它必须是两种不同数据结构的综合。但它应该是哈希表+二叉树还是trie +排序数组还是别的什么?

5 个答案:

答案 0 :(得分:6)

平衡树(例如红黑树)和后缀树(或后缀数组)的组合。

  • 平衡树:操作1,2(实现为删除+插入),3和5.
  • 后缀树:操作4.

注意:哈希表将无法有效地支持操作5.

我认为你很难实现后缀树。你可以使用Mark Nelson's C++ implementation of Ukkonen's algorithm,但它有内存泄漏,基本上是一个单例,所以你需要在准备好生产之前清理它。即使您修好了它,您也需要对其进行调整,以便它与您的其他人一起使用"数据结构(在我的提议中是平衡树)而不是一个大的普通字符串。

如果您比第4次操作更频繁地进行操作1和/或您可以使用线性操作4,我建议您使用后缀树跳过整个复杂功能并直接遍历数据结构。

答案 1 :(得分:4)

对于前三个操作,哈希表可能是个好主意。

对于第4个操作(选择键的一部分),您可能必须以不同方式编写散列函数。是的,哈希函数,用于从给定键中查找/计算哈希值。 由于您希望支持部分匹配并且您的密钥是字符串,因此您可能希望使用后缀树或特里结构。

对于第5个操作(ith max element),您可能希望维护与hash-table交互的堆或已排序的Linked-list(或skip-list)。

您还必须查看各种用例,并找出应优化的操作。例如:如果你对part_of_key操作有很多查询,你应该使用Suffix-tree / LC-trie类型的结构,这样可以得到很好的结果。但是,您的查找操作可能不会很快,因为它需要O(logN)而不是常量查找。

总而言之,您需要集成hash-table,heap和suffix树来实现所有操作。

答案 2 :(得分:3)

虽然确切的答案取决于您的操作频率,但您的选项列表应包含suffix array

答案 3 :(得分:1)

我认为有几个快捷方式的特里是最好的。

trie已经尽可能快地支持1-3(密钥的长度)。

对于4,我会向可以从该字符输入的所有trie节点添加256个元素的附加表。通过这种方式,我可以快速查找字符串的各个部分,而无需像后缀树那样明确地构造或存储后缀。

对于5,我会在叶子上添加一个链表,当插入数据时,它会更新。您可能需要另外引用列表的头部和trie中的反向链接以找到正确的值并获取密钥。

内存复杂性不会随着这些添加而改变,因为它受特里节点数量的限制。但是,由于链表的排序效率低,插入所用的时间可能会发生变化。

最终的结构应该被称为hash-leaf-trie。但我不想实施这样的野兽。

答案 4 :(得分:1)

我认为有效的数据库将是经过修改的trie,具有双向链接[可以从叶子到根并重建单词],并且每个终止节点将具有额外的“值”字段。
您还需要一个多图[即地图,其中每个值都是一组地址]。
键将被安排在树中,并且该组值将基于散列。 [在jave中,它应该类似于TreeMap<Integer,HashSet<Node>>]

伪代码:[好吧,非常伪...它只显示每个操作的一般想法]。

Insert(key):
1. Insert a word into the trie
2. add the value `1` to the terminating node.
3. add the terminating field to the multimap [i.e. map.add(1,terminating_node_address)]
Increment(key):
1. read the word in the trie, and store the value from the terminating node as temp.
2. delete the entree (temp,terminating_node_address) from the multimap.
3. insert the entree (temp+1,terminating_node_address) to the multimap.
4. increase the value of the terminating node's address by 1.
Find(key):
1. find the terminating node for the key in the trie, return its value.
Select(part_of_key):
1. go to the last node you can reach with part_of_key.
2. from this node: do BFS and retrieve all possible words [store them in an empty set you will later return].
Max(i):
1. find the i'th biggest element key in the multimap.
2. choose an arbitrary address, and return get the relevant terminating word node.
3. build the string by following the uplinks to the root, and return it.

<强>复杂性:
n为字符串数,k为最大值,S为字符串长度。
插入:O(S) [插入是O(S)]。可以缓存映射中的最小元素(1),因此可以在O(1)处访问。
增量:O(S + logk):查找trie中的字符串是O(S),找到相关键是O(logk),从哈希集中删除元素是O(1),找到树中的下一个元素也是O(logk)[实际上可以是O(1),对于基于skip-list的映射],并且插入值是O(1)。请注意,通过将节点链接到地图中的相关键,复杂性甚至可以提高到O(S),因为实际上不需要搜索。
查找:O(S):只需在trie中找到并返回值即可。
选择:O(S * n):在最坏的情况下,您需要检索所有元素,这会强制您迭代整个线索以查找所有单词。
最大:O(logk + S):在有序地图中找到一个关键字,重建该单词。

编辑:请注意,在这里,我假设发现(i)你想要第i个不同的最大元素。如果不是这种情况,则需要稍微修改集合,并使用允许重复键的多图[而不是设置为值]。这将使所有基于树的操作O(logn)改为O(logk),在我看来仍然有效...