字典数据结构+快速复杂度方法
Dictionary data structure + fast complexity methods
我正在尝试从头开始构建一个数据结构,该数据结构能够容纳大量字典(words/characters)。
"words"可以由任意多的字符组成。
词典需要标准方法,例如搜索、插入、删除。
我需要这些方法的时间复杂度 比 O(log(n))
好 ,所以在 O(log(n))
到 O(1)
之间,例如 log(log(n))
其中 n = 字典大小(元素数量)
我研究了各种树结构,例如具有 log(n)
方法(不够快)的 b-tree 以及似乎最适合字典的 trie,但由于事实单词可以任意大它似乎喜欢它的复杂性不会比 log(n)
.
快
如果可以,请提供任何解释
trie 对内存的要求很高,但访问时间通常 比O(log n)
快。
如果我没记错的话,访问时间取决于单词的长度,而不是结构中单词的数量。
效率和内存消耗还取决于您选择使用的 trie 的具体实现。那里有一些非常有效的实现。
有关 Tries 的更多信息,请参阅:
http://en.wikipedia.org/wiki/Trie
http://algs4.cs.princeton.edu/52trie/
http://algs4.cs.princeton.edu/52trie/TrieST.java.html
https://www.topcoder.com/community/data-science/data-science-tutorials/using-tries/
如果您的问题是如何实现尽可能少的字符串比较,那么散列 table 可能是一个很好的答案,因为它需要接近 O(1)
字符串比较。请注意,散列键值所花费的时间与字符串长度成正比,字符串比较的时间也是如此。
但这并不是什么新鲜事。我们可以为长字符串做得更好吗?更准确地说,我们假设字符串长度以 M
为界。我们还将假设每个字符串的长度都是已知的(对于长字符串,这可能会有所不同)。
首先注意搜索时间受字符串长度限制,最坏情况下为 Ω(M)
:比较两个字符串可能需要比较所有字符,因为字符串只能在最后一个字符不同比较。另一方面,在最好的情况下,比较可以立即结束,要么是因为长度不同,要么是因为比较的第一个字符的字符串不同。
现在你可以这样推理:考虑字典中的整组字符串,找到它们不同的第一个字符的位置。根据此字符的值,您将分解为多个子集。你可以递归地继续这个分解,直到你得到单例。
例如,
able
about
above
accept
accident
accompany
组织为
*bl*
*bou*
*bov*
*c*e**
*c*i****
*c*o*****
其中星号代表刚刚忽略的字符,其余字符用于区分。
如您所见,在此特定示例中,两个或三个字符比较足以识别字典中的任何单词。
这种表示可以描述为有限状态自动机,这样在每个状态下您都知道接下来要检查哪个字符以及可能的结果是什么,从而导致下一个状态。它具有 K
元树结构(其中 K
是字母表的大小)。
为了高效实施,每个状态都可以由决策字符的位置和指向下一个状态的链接的 数组 表示。实际上,这是一个 trie 结构,具有路径压缩。 (正如@[=58=所说,trie结构有多种变体。)
我们如何使用它?有两种情况:
1) 已知搜索字符串在字典中:那么简单遍历树就可以保证找到它。在与树中相应叶子的深度 O(D)
相等的字符比较次数之后,它将这样做,其中 D
就是这个深度。这可能是一笔非常可观的节省。
2) 搜索字符串可能不在字典中:在遍历树的过程中,您可以观察到早期拒绝;否则,最后您会找到一个潜在的匹配项。那么你就不可避免地要进行详尽的比较,最好的情况是O(1)
,最坏的情况是O(M)
。 (对于随机字符串,平均 O(M)
,但对于现实世界的分布可能更好。)但您将与单个字符串进行比较,再也不会了。
除了该设备之外,如果您的密钥长度分布稀疏,维护密钥长度的散列 table 可能会很有用,这样可以立即拒绝搜索字符串。
作为最后的评论,请注意,此解决方案的成本不是 N
的直接函数,并且 M
中的时间次线性很可能可以通过 sui[=59= 实现] 启发式利用字符串的特定分布。
我正在尝试从头开始构建一个数据结构,该数据结构能够容纳大量字典(words/characters)。
"words"可以由任意多的字符组成。
词典需要标准方法,例如搜索、插入、删除。
我需要这些方法的时间复杂度 比 O(log(n))
好 ,所以在 O(log(n))
到 O(1)
之间,例如 log(log(n))
其中 n = 字典大小(元素数量)
我研究了各种树结构,例如具有 log(n)
方法(不够快)的 b-tree 以及似乎最适合字典的 trie,但由于事实单词可以任意大它似乎喜欢它的复杂性不会比 log(n)
.
如果可以,请提供任何解释
trie 对内存的要求很高,但访问时间通常 比O(log n)
快。
如果我没记错的话,访问时间取决于单词的长度,而不是结构中单词的数量。
效率和内存消耗还取决于您选择使用的 trie 的具体实现。那里有一些非常有效的实现。
有关 Tries 的更多信息,请参阅:
http://en.wikipedia.org/wiki/Trie
http://algs4.cs.princeton.edu/52trie/
http://algs4.cs.princeton.edu/52trie/TrieST.java.html
https://www.topcoder.com/community/data-science/data-science-tutorials/using-tries/
如果您的问题是如何实现尽可能少的字符串比较,那么散列 table 可能是一个很好的答案,因为它需要接近 O(1)
字符串比较。请注意,散列键值所花费的时间与字符串长度成正比,字符串比较的时间也是如此。
但这并不是什么新鲜事。我们可以为长字符串做得更好吗?更准确地说,我们假设字符串长度以 M
为界。我们还将假设每个字符串的长度都是已知的(对于长字符串,这可能会有所不同)。
首先注意搜索时间受字符串长度限制,最坏情况下为 Ω(M)
:比较两个字符串可能需要比较所有字符,因为字符串只能在最后一个字符不同比较。另一方面,在最好的情况下,比较可以立即结束,要么是因为长度不同,要么是因为比较的第一个字符的字符串不同。
现在你可以这样推理:考虑字典中的整组字符串,找到它们不同的第一个字符的位置。根据此字符的值,您将分解为多个子集。你可以递归地继续这个分解,直到你得到单例。
例如,
able
about
above
accept
accident
accompany
组织为
*bl*
*bou*
*bov*
*c*e**
*c*i****
*c*o*****
其中星号代表刚刚忽略的字符,其余字符用于区分。
如您所见,在此特定示例中,两个或三个字符比较足以识别字典中的任何单词。
这种表示可以描述为有限状态自动机,这样在每个状态下您都知道接下来要检查哪个字符以及可能的结果是什么,从而导致下一个状态。它具有 K
元树结构(其中 K
是字母表的大小)。
为了高效实施,每个状态都可以由决策字符的位置和指向下一个状态的链接的 数组 表示。实际上,这是一个 trie 结构,具有路径压缩。 (正如@[=58=所说,trie结构有多种变体。)
我们如何使用它?有两种情况:
1) 已知搜索字符串在字典中:那么简单遍历树就可以保证找到它。在与树中相应叶子的深度 O(D)
相等的字符比较次数之后,它将这样做,其中 D
就是这个深度。这可能是一笔非常可观的节省。
2) 搜索字符串可能不在字典中:在遍历树的过程中,您可以观察到早期拒绝;否则,最后您会找到一个潜在的匹配项。那么你就不可避免地要进行详尽的比较,最好的情况是O(1)
,最坏的情况是O(M)
。 (对于随机字符串,平均 O(M)
,但对于现实世界的分布可能更好。)但您将与单个字符串进行比较,再也不会了。
除了该设备之外,如果您的密钥长度分布稀疏,维护密钥长度的散列 table 可能会很有用,这样可以立即拒绝搜索字符串。
作为最后的评论,请注意,此解决方案的成本不是 N
的直接函数,并且 M
中的时间次线性很可能可以通过 sui[=59= 实现] 启发式利用字符串的特定分布。