我得到trie背后的概念。但是在实施方面我有点困惑。
我认为构建Trie
类型最明显的方法是让Trie
维护内部Dictionary<char, Trie>
。事实上,我已经用这种方式编写了一个,并且可以工作,但是......这看起来有点矫枉过正。我的印象是trie应该是轻量级的,并且对于每个节点单独Dictionary<char, Trie>
对我来说似乎不是很轻量级。
有没有更合适的方法来实现我缺少的这个结构?
更新:好的!根据Jon和leppie的非常有用的意见,这是我到目前为止所提出的:
(1)我有Trie
类型,其中包含_nodes
类型的私有Trie.INodeCollection
成员。
(2)Trie.INodeCollection
接口具有以下成员:
interface INodeCollection
{
bool TryGetNode(char key, out Trie node);
INodeCollection Add(char key, Trie node);
IEnumerable<Trie> GetNodes();
}
(3)此接口有三种实现方式:
class SingleNode : INodeCollection
{
internal readonly char _key;
internal readonly Trie _trie;
public SingleNode(char key, Trie trie)
{ /*...*/ }
// Add returns a SmallNodeCollection.
}
class SmallNodeCollection : INodeCollection
{
const int MaximumSize = 8; // ?
internal readonly List<KeyValuePair<char, Trie>> _nodes;
public SmallNodeCollection(SingleNode node, char key, Trie trie)
{ /*...*/ }
// Add adds to the list and returns the current instance until MaximumSize,
// after which point it returns a LargeNodeCollection.
}
class LargeNodeCollection : INodeCollection
{
private readonly Dictionary<char, Trie> _nodes;
public LargeNodeCollection(SmallNodeCollection nodes, char key, Trie trie)
{ /*...*/ }
// Add adds to the dictionary and returns the current instance.
}
(4)首次构建Trie
时,其_nodes
成员为null
。第一次调用Add
会创建一个SingleNode
,然后根据上述步骤从{{}}}调用{<1}}。
这有意义吗?这感觉就像是一种改进,因为它有点减少了Add
的“庞大”(节点不再是完整的Trie
对象,直到它们有足够的数量孩子的)。然而,它也变得更加复杂。它太复杂了吗?我是否采取了一条复杂的路线来实现应该直截了当的事情?
答案 0 :(得分:4)
嗯,您需要每个节点都有有效实现IDictionary<char, Trie>
的内容。您可以编写自己的自定义实现,根据其具有的子节点数来改变其内部结构:
char
和Trie
List<Tuple<char, Trie>>
或LinkedList<Tuple<char,Trie>>
Dictionary<char, Trie>
(刚看到leppie的答案,我相信这是他所谈论的那种混合方法。)
答案 1 :(得分:3)
在我看来,将它作为一个字典实现,并没有实现一个Trie - 它正在实现一个字典词典。
当我实施了一个trie时,我已经按照Damien_The_Unbeliever(+1那里)建议的方式完成了它:
public class TrieNode
{
TrieNode[] Children = new TrieNode[no_of_chars];
}
这理想情况下要求您的trie仅支持no_of_chars
指示的有限字符子集,并且您可以将输入字符映射到输出索引。例如。如果支持A-Z,那么你自然会将A映射到0,Z映射到25。
当您需要添加/删除/检查节点的存在时,您可以执行以下操作:
public TrieNode GetNode(char c)
{
//mapping function - could be a lookup table, or simple arithmetic
int index = GetIndex(c);
//TODO: deal with the situation where 'c' is not supported by the map
return Children[index];
}
在实际情况中,我已经看到了这个优化,例如,AddNode将采用ref TrieNode
,以便可以根据需要新建节点并自动放入父级TrieNode的Children
中正确的地方。
您也可以使用三元搜索树,因为trie的内存开销可能非常疯狂(特别是如果您打算支持所有32k的unicode字符!)并且TST性能相当令人印象深刻(并且还支持前缀和放大器) ;通配符搜索以及汉明搜索)。同样,TST可以原生支持所有unicode字符,而无需进行任何映射;因为它们使用大于/小于/等于操作而不是绝对索引值。
我接受了代码from here并略微调整了它(它是在泛型之前编写的)。
我想你会对TST感到惊喜;一旦我实施了一个,我完全离开了Tries。
唯一棘手的事情是保持TST的平衡;你没有Tries的问题。
答案 2 :(得分:3)
如果您的角色来自有限集(例如只有大写拉丁字母),那么您可以存储26个元素数组,每个查找只是
Trie next = store[c-'A']
其中c是当前查找字符。
答案 3 :(得分:2)
有几种方法,但使用单链接列表可能是最简单和最轻量级的。
我会做一些测试来查看每个节点都有的子节点数量。如果不多(比如20或更少),链接列表方法应该比哈希表更快。您也可以根据子节点的数量进行混合方法。