trie 和 radix trie 数据结构是否相同?
如果它们相同,那么radix trie(AKA Patricia trie)的含义是什么?
答案 0 :(得分:100)
基数树是trie的压缩版本。在trie中,在每个边缘上写一个字母,而在PATRICIA树(或基数树)中存储整个单词。
现在,假设您有hello
,hat
和have
字样。要将它们存储在 trie 中,它看起来像:
e - l - l - o
/
h - a - t
\
v - e
你需要九个节点。我已将字母放在节点中,但事实上它们标记了边缘。
在基数树中,您将拥有:
*
/
(ello)
/
* - h - * -(a) - * - (t) - *
\
(ve)
\
*
并且您只需要五个节点。在上图中,节点是星号。
总的来说,基数树需要更少的内存,但实现起来比较困难。否则两者的用例几乎相同。
答案 1 :(得分:61)
我的问题是 Trie 数据结构和 Radix Trie 是否相同?
简而言之,没有。类别 Radix Trie 描述了 Trie 的特定类别,但这并不意味着所有尝试都是基数尝试。
如果它们相同,那么Radix trie(又名Patricia Trie)的含义是什么?
我认为你打算在你的问题中写下 aren&#t; ,因此我的纠正。
同样,PATRICIA表示特定类型的基数trie,但并非所有基数尝试都是PATRICIA尝试。
" Trie树"描述了适合用作关联数组的树数据结构,其中分支或边对应于键的部分。这里, parts 的定义相当模糊,因为尝试的不同实现使用不同的位长来对应边。例如,二进制trie每个节点有两个边对应0或1,而16路trie每个节点有16个边对应4位(或十六进制数:0x0到0xf)。 p>
这张从维基百科中检索到的图表,似乎描绘了一个带有(至少)钥匙'' to',' tea'& #39; ted',' ten'和'旅馆'插入:
如果这个特里要存储钥匙的物品,那么,' i' i'或者'在'需要在每个节点上提供额外的信息,以区分具有实际值的无效节点和节点。
" Radix trie"似乎描述了一种形式的trie,它凝聚了常见的前缀部分,正如Ivaylo Strandjev在他的回答中所描述的那样。考虑一个256路的trie,为键索引"微笑","微笑","微笑"并且"微笑"使用以下静态分配:
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
。对于这四个项目,总共有十四个节点。但是,它们都具有共同的前四个字节(对应于前四个节点)。通过压缩这四个字节来创建对应于root
的{{1}},已经优化了四个节点访问。这意味着更少的内存和更少的节点访问,这是一个非常好的指示。可以递归地应用优化以减少访问不必要的后缀字节的需要。最后,您只能比较搜索键和由trie 索引的位置处的索引键之间的差异。这是一个基数。
['s']['m']['i']['l']
要检索项目,每个节点都需要一个位置。使用搜索键"微笑"我是root = smil_dummy;
root['e'] = smile_item;
root['e']['d'] = smiled_item;
root['e']['s'] = smiles_item;
root['i'] = smiling_item;
的{{1}},访问root.position
,恰好是root["smiles"[4]]
。我们将其存储在名为root['e']
的变量中。 current
为5,这是current.position
和"smiled"
之间差异的位置,因此下一次访问将为"smiles"
。这将我们带到root["smiles"[5]]
,以及我们字符串的结尾。我们的搜索已经终止,并且已经检索了该项目,只有三个节点访问而不是八个。
PATRICIA trie是基数尝试的变体,其中应该只有smiles_item
个节点用于包含n
个项目。在上面粗略演示的基数trie伪代码中,总共有五个节点:n
(这是一个无效节点;它不包含实际值),root
,root['e']
,{{1 }和root['e']['d']
。在PATRICIA特里,应该只有四个。让我们看看这些前缀如何通过二进制查看它们可能会有所不同,因为PATRICIA是一种二进制算法。
root['e']['s']
让我们考虑按照上面给出的顺序添加节点。 root['i']
是此树的根。差异,加粗以使其更容易发现,位于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 ...
的最后一个字节,位于第36位。直到此时,我们所有节点都具有相同的前缀。 smile_item
属于"smile"
。 smiled_node
和smile_node[0]
之间的差异发生在第43位,其中"smiled"
有一个' 1'位,"smiles"
是"smiles"
。
不是使用smiled_node[1]
作为分支和/或额外的内部信息来表示搜索何时终止,而是将分支链接回向上树的某处,因此搜索会在偏移到test 减少而不是增加。这里是这样一个树的简单图表(虽然PATRICIA实际上更像是一个循环图,而不是一棵树,正如你所见),它包含在Sedgewick'下面提到的书:
虽然PATRICIA的一些技术属性在过程中丢失(即任何节点包含与之前节点的公共前缀),但是涉及变量长度的密钥的更复杂的PATRICIA算法是可能的:
通过这样的分支,有许多好处:每个节点都包含一个值。这包括根。结果,代码的长度和复杂性变得更短,并且实际上可能更快一些。遵循至少一个分支和最多smiles_node
个分支(其中NULL
是搜索关键字中的位数)以定位项目。节点 tiny ,因为它们每个只存储两个分支,这使得它们非常适合缓存局部性优化。到目前为止,这些属性使PATRICIA成为我最喜欢的算法......
为了减轻我即将发生的关节炎的严重程度,我在这里简短地说明这个描述,但是如果你想了解更多关于PATRICIA的信息,你可以查阅诸如“计算机的艺术”之类的书籍。编程,第3卷"由Donald Knuth或{your-favorite-language}中的任何"算法,1-4和#34;由Sedgewick。
答案 2 :(得分:16)
<强> TRIE:强>
我们可以使用搜索方案,而不是将整个搜索关键字与所有现有关键字(例如哈希方案)进行比较,我们也可以比较搜索关键字的每个字符。按照这个想法,我们可以构建一个结构(如下所示),它有三个现有的键 - “ dad ”,“ dab ”和“ cab ”。
[root]
...// | \\...
| \
c d
| \
[*] [*]
...//|\. ./|\\... Fig-I
a a
/ /
[*] [*]
...//|\.. ../|\\...
/ / \
B b d
/ / \
[] [] []
(cab) (dab) (dad)
这实际上是一个带有内部节点的M-ary树,表示为[*]和叶节点,表示为[]。 此结构称为 trie 。每个节点的分支决策可以保持等于字母表中唯一符号的数量,例如R.对于小写英文字母a-z,R = 26;对于扩展的ASCII字母,R = 256,对于二进制数字/字符串R = 2。
紧凑型TRIE:
通常, trie 中的节点使用size = R的数组,因此当每个节点具有较少的边时会导致内存浪费。为了避免记忆问题,提出了各种建议。根据这些变体, trie 也被命名为“ compact trie ”和“压缩的trie ”。虽然一致的命名法很少见,但是当节点具有单个边缘时,通过对所有边进行分组来形成紧凑 trie 的最常见版本。使用这个概念,上面的(图-I) trie ,键“dad”,“dab”和“cab”可以采用以下形式。
[root]
...// | \\...
| \
cab da
| \
[ ] [*] Fig-II
./|\\...
| \
b d
| \
[] []
请注意,'c','a'和'b'中的每一个都是其对应父节点的唯一边缘,因此,它们聚集成单个边缘“cab”。类似地,'d'和''合并为标记为“da”的单边。
Radix Trie:
数学中的术语 radix 表示数字系统的基础,它实质上表示表示该系统中任何数字所需的唯一符号的数量。例如,十进制系统是基数十,二进制系统是基数二。使用类似的概念,当我们对通过底层表示系统的唯一符号的数量表征数据结构或算法感兴趣时,我们用术语“基数”标记该概念。例如,某些排序算法的“基数排序”。在同一逻辑行中, trie 的所有变体的特征(例如深度,内存需求,搜索未命中/命中运行时等)都取决于底层的基数字母表,我们可以将它们称为基数“trie's”。例如,当使用字母az时,未压缩的以及压缩的 trie ,我们可以将其称为基数26 trie 。任何仅使用两个符号(传统上为“0”和“1”)的trie都可以称为基数2 trie 。然而,不知何故,许多文献仅限于使用“Radix Trie”一词来压缩 trie 。
PATRICIA Tree / Trie的前奏:
值得注意的是,即使字符串作为键也可以使用二进制字母表示。如果我们假设ASCII编码,则可以通过按顺序编写每个字符的二进制表示来以二进制形式编写密钥“dad”,例如“ 01100100 01100001 01100100 “通过顺序写入'd','a'和'd'的二进制形式。
使用此概念,可以形成 trie (带有Radix Two)。下面我们使用简化的假设来描述这个概念,即字母'a','b','c'和''来自较小的字母而不是ASCII。
图III的注释: 如上所述,为了使描述容易,让我们假设一个只有4个字母{a,b,c,d}的字母表,它们相应的二进制表示分别是“00”,“01”,“10”和“11”。这样,我们的字符串键“dad”,“dab”和“cab”分别变为“110011”,“110001”和“100001”。对此的描述将如下图III所示(从左到右读取位,就像从左到右读取字符串一样)。
[root]
\1
\
[*]
0/ \1
/ \
[*] [*]
0/ /
/ /0
[*] [*]
0/ /
/ /0
[*] [*]
0/ 0/ \1 Fig-III
/ / \
[*] [*] [*]
\1 \1 \1
\ \ \
[] [] []
(cab) (dab) (dad)
PATRICIA Trie / Tree:
如果我们使用单边压缩来压缩上面的二进制 trie (图-III),它将比上面显示的节点少得多,但是节点仍然会超过3,它包含的键数。 Donald R. Morrison 发现(1968年)一种创新方式,使用二进制 trie 来仅描绘N键N个节点,他将此数据结构命名为 PATRICIA 。他的trie结构基本上摆脱了单边(单向分支);在这样做的同时,他还摆脱了两种节点的概念 - 内部节点(不描绘任何键)和叶节点(描绘键)。与上面解释的压缩逻辑不同,他的线索使用不同的概念,其中每个节点包括指示要跳过多少位的密钥以进行分支决策。他的PATRICIA trie的另一个特点是它不存储密钥 - 这意味着这样的数据结构不适合回答诸如列出与给定前缀匹配的所有密钥之类的问题,但是很好查找如果某个密钥在trie中存在或不存在。尽管如此,从那时起,Patricia Tree或Patricia Trie这个术语已被用于许多不同但相似的意义上,例如,表示一个紧凑的trie [NIST],或表示基数为2的两基数[如微妙所示]在WIKI的方式]等等。
Trie可能不是Radix Trie:
三元搜索Trie (又名三元搜索树)通常缩写为 TST 是一种数据结构(由 J。Bentley提出和 R.Sepgewick )看起来非常类似于具有三向分支的trie。对于这样的树,每个节点具有特征字母'x',使得分支决定由键的字符是否小于,等于或大于'x'来驱动。由于这种固定的3路分支功能,它为trie提供了一种节省内存的替代方案,特别是当R(基数)非常大时,例如Unicode字母表。有趣的是,与(R-way) trie 不同,TST没有受R影响的特征。例如,TST的搜索未命中 ln( N)而不是 log R (N)用于R-way Trie。与R-way trie 不同,TST的内存要求 NOT 也是R的函数。所以我们应该小心地将TST称为基数。我个人认为我们不应该把它称为基数 - 因为没有(据我所知)其特征受其基础字母的基数R的影响。