哪个更有效率。像这样的Trie结构:
struct TrieNode
{
char letter;
bool isWord;
TrieNode* subNodes[26];
};
或像这样的Trie结构:
struct TrieNode
{
char letter;
bool isword;
map<int, TrieNode*> subNodes;
};
或者只是一个更好的实施...... 还有,有人能给我一个解释吗?
答案 0 :(得分:1)
我会使用第一个,为了简单和速度,但可以想象第二个可以节省空间。
在任一代码中都不需要char letter
元素。
它是多余的,因为你查找单词的方式是获取键的一个字母,并将其用作subNode数组的索引或地图中的键,以便选择一个subNode。
无论哪种方式,您都不需要查看letter
。
你知道单词是否在trie中的方式是你是否遇到了一个空子节点,或者你是否在没有点击isWord
子节点的情况下耗尽你的密钥。
顺便说一句,如果你的trie不包含太多的单词,并且如果它不经常变化,你将通过将其转换为ad-hoc代码来节省大约一个数量级的速度。
// XYZ is the prefix string that corresponds to a node in the trie
bool XYZFunc(char* key){
switch (*key){
case '\0': return true /* if XYZ is a valid word, else false */; break;
case 'a': return XYZaFunc(key+1); break;
case 'b': return XYZbFunc(key+1); break;
// etc. etc.
}
}
这可能是很多函数,但在合理的范围内,编译器应该能够处理它。然后查找一个单词,你只需调用顶级函数,它返回true或false。在每个节点,编译器将确定它是否需要跳转表,因此您不必担心。
答案 1 :(得分:1)
我曾经采用第一种方法(即每个节点都有一个子字母用于字母表的每个可能的字母),但是意识到这是非常低效的(空间明智的)并假设你总是有一个恒定的算法。
如果您改为使用链表替换数组(然后对其进行操作),您可以使用二叉树实现(但是结构仍然比传统的二进制树更有效,因为您不是在每个节点使用字符串比较,并且因为你的密钥空间重叠(找到“the”并找到“then”以相同的比较开始)。
即考虑:
struct TrieNode
{
char key;
char *val; /* This is null unless we are an "end node" - you could use the Bool as you do, but I've found this a bit simpler */
struct TrieNode *siblings; /* traversing this is checking different characters at this position in the string */
struct TrieNode *children; /* Travesring this list is looking at subsequent positions in the list */
};
虽然在最坏的情况下,这种方法的效率开始爆发,但字母表的大小决定了要检查的兄弟姐妹的最大数量,并且对于自然语言(与基因组相对)的排序,trie通常会非常稀疏,所以我们永远不会接近实际的最坏情况。