我的问题是 Trie 数据结构和 Radix Trie 是不是一回事?
简而言之,没有。 Radix Trie 类别描述了 Trie 的特定类别,但这并不意味着所有尝试都是基尝试。
如果它们[n't]相同,那么 Radix trie(又名 Patricia Trie)是什么意思?
我假设你的意思是在你的问题中写 不是,因此我更正了。
同样,PATRICIA 表示特定类型的基数树,但并非所有基数树都是 PATRICIA 树。
什么是尝试?
“Trie”描述了一种适合用作关联数组的树数据结构,其中分支或边对应于键的部分。 parts 的定义在这里相当模糊,因为尝试的不同实现使用不同的位长来对应边。例如,二叉树的每个节点有两条边,对应于 0 或 1,而 16 路特里树的每个节点有十六条边,对应于 4 位(或十六进制数字:0x0 到 0xf)。
从 Wikipedia 检索到的这张图似乎描绘了一个带有(至少)键 'A'、'to'、'tea'、'ted'、'ten'、'i'、'in' 和'客栈'插入:
如果此树要存储键“t”或“te”的项目,则需要在每个节点上提供额外信息(图中的数字)以区分空节点和具有实际值的节点。
什么是基数?
“Radix trie”似乎描述了一种压缩公共前缀部分的 trie 形式,正如 Ivaylo Strandjev 在他的回答中所描述的那样。考虑一个 256 路的 trie,它使用以下静态分配索引键“smile”、“smiled”、“smiles”和“smiling”:
root['s']['m']['i']['l']['e']['\0'] = smile_item;
root['s']['m']['i']['l']['e']['d']['\0'] = smiled_item;
root['s']['m']['i']['l']['e']['s']['\0'] = smiles_item;
root['s']['m']['i']['l']['i']['n']['g']['\0'] = smiling_item;
每个下标访问一个内部节点。这意味着要检索smile_item,您必须访问七个节点。八个节点访问对应smiled_item 和smiles_item,九个对应smiling_item。对于这四个项目,总共有十四个节点。但是,它们都有前四个字节(对应于前四个节点)。通过压缩这四个字节来创建一个对应于['s']['m']['i']['l'] 的root,四个节点访问已经被优化掉了。这意味着更少的内存和更少的节点访问,这是一个很好的指示。可以递归地应用优化以减少访问不必要的后缀字节的需要。最终,您只需要比较 trie 索引位置的搜索键和索引键之间的差异。这是一个基数。
root = smil_dummy;
root['e'] = smile_item;
root['e']['d'] = smiled_item;
root['e']['s'] = smiles_item;
root['i'] = smiling_item;
要检索项目,每个节点都需要一个位置。使用搜索关键字“smiles”和root.position 4,我们访问root["smiles"[4]],恰好是root['e']。我们将其存储在一个名为current 的变量中。 current.position是5,就是"smiled"和"smiles"的区别所在的位置,所以下次访问会是root["smiles"[5]]。这将我们带到smiles_item,以及我们字符串的结尾。我们的搜索已终止,项目已被检索到,只有 3 个节点访问,而不是 8 个。
什么是 PATRICIA trie?
PATRICIA trie 是基数尝试的一种变体,其中应该只存在用于包含 n 项的 n 节点。在上面我们粗略演示的 radix trie 伪代码中,总共有五个节点:root(这是一个空节点;它不包含实际值)、root['e']、root['e']['d']、root['e']['s'] 和 root['i']。在 PATRICIA trie 中应该只有四个。由于 PATRICIA 是一种二进制算法,因此让我们以二进制形式查看这些前缀可能有何不同。
smile: 0111 0011 0110 1101 0110 1001 0110 1100 0110 0101 0000 0000 0000 0000
smiled: 0111 0011 0110 1101 0110 1001 0110 1100 0110 0101 0110 0100 0000 0000
smiles: 0111 0011 0110 1101 0110 1001 0110 1100 0110 0101 0111 0011 0000 0000
smiling: 0111 0011 0110 1101 0110 1001 0110 1100 0110 1001 0110 1110 0110 0111 ...
让我们考虑按上面显示的顺序添加节点。 smile_item 是这棵树的根。不同之处(粗体显示以便更容易发现)位于"smile" 的最后一个字节,位于第 36 位。到目前为止,我们所有的节点都有相同的前缀。 smiled_node 属于 smile_node[0]。 "smiled" 和 "smiles" 之间的区别出现在第 43 位,其中"smiles" 有一个“1”位,所以smiled_node[1] 是smiles_node。
而不是使用NULL 作为分支和/或额外的内部信息来表示搜索何时终止,分支链接回up树的某处,因此当要测试的偏移量减少而不是增加。这是一个这样的树的简单图(虽然 PATRICIA 确实更像是一个循环图,而不是一棵树,正如你将看到的),它包含在下面提到的 Sedgewick 的书中:
一个更复杂的涉及变长密钥的 PATRICIA 算法是可能的,尽管 PATRICIA 的一些技术特性在此过程中丢失了(即任何节点都包含与它之前的节点相同的前缀):
通过这样的分支,有很多好处:每个节点都包含一个值。这包括根。结果,代码的长度和复杂性变得更短,实际上可能更快一些。遵循至少一个分支和最多k 分支(其中k 是搜索关键字中的位数)来定位项目。这些节点很小,因为它们每个只存储两个分支,这使得它们非常适合缓存局部性优化。这些属性使 PATRICIA 成为我迄今为止最喜欢的算法...
我将在此处缩短此描述,以减少即将发生的关节炎的严重程度,但如果您想了解更多关于 PATRICIA 的信息,可以查阅诸如“计算机编程艺术,第 3 卷”之类的书籍由 Donald Knuth 撰写,或 Sedgewick 撰写的任何“{your-favourite-language} 中的算法,第 1-4 部分”。