今天看到了最长公共前缀树的时候就突然想到了前几个星期学的前缀树。所以打算来写写。
试想有这样一种场景,我们现在手里有10W个单词,并且里面有重复的单词,如果我们使用传统的遍历手法查找一个单词,那么可能没等查出来交卷铃声已经响了。
或许有人会说使用哈希表,但是存在重复的单词,这个效率还是欠缺了点。
所以就引入了今天的主角—字典树。
字典树又称之为前缀树,是一个哈希表的变种,优于哈希表的查找速度。但是他的空间复杂度是十分复杂的,所以是一种空间换时间的做法。
字典树原理:
字典数将相同前缀的单词做为共用的前缀,如果找不到这个前缀,则在另外一个结点上插入这个单词。并且在这个单词结尾的结点加上1。
字典树的基础结构
树是由结点和和根组成。这个大家都应该直到就不多赘述了。
- 先创建出一个树的结点,这个结点包含该单词被经过多少次的路径,以及一个end结尾来标记是否有单词在这里结尾。
- 需要注意的是,下面的代码并不把字母放在结点的位置,而是放在路径上。
我们来看看我们代码实现的基础结构。
以下使用C++编写,不影响阅读
struct TreeNode // 创建结点
{
TreeNode *next[MaxSize];
int end;
int path;
TreeNode() // 初始化结点
{
end = 0;
path = 0;
for(int i = 0;i< MaxSize;i++)
next[i] = new TreeNode;
}
};
既然是一颗树,要想实现他的功能那么自然需要有增删查的功能。下面我们来实现这样的功能。
#define MaxSize 26
class Trie // 创建树
{
private:
TreeNode *root; // 根节点
public:
Trie() // 初始化
{
root = new TreeNode;
}
void insert_Node(string word); // 插入
void Delete_Node(string word); // 删除
bool search(string word); // 查找
};
插入功能
我们可以在创建树上看到一个创建一个26大小的next数组。这个是指向下一个结点的指针,我们假设的是我们输入的都是小写字母。所以26个字符。
- 我们插入一个单词的时候,插入的是他的各各字母,根据字母的ASCII减去 ’a‘的ASCII,这样得到的就一定是0-25的数值。那么就可以知道这个字母哪个。
- 如果这个结点不为NULL,那么就跳这个结点的下一个结点,并且path都加上1.
直到全部字母插入完毕,在结束字母的结点上 end+1。
接下来我们阅读代码吧。
以下是C++代码不影响阅读
void Trie::insert_Node(string word)
{
if (word == "")
return;
int index = 0;
TreeNode *node = root;
for (int i = 0; i < word.length(); i++)
{
index = word[i] = 'a';
if (node)
node = new TreeNode;
node = node->next[index];
node->path++;
}
node->end++;
}
查找功能
查找功能跟插入代码相似,怎么插入的就怎么查找。如果查找不到返回false,找到返回true。
bool Trie::search(string word)
{
if (word == "")
return false;
TreeNode *node = root;
int index = 0;
for (int i = 0; i < word.length(); i++)
{
index = word[0] - 'a';
if (node->next[index] == NULL)
return false;
node = node->next[index];
}
return true;
}
由于代码跟插入代码相似就不多说了。
删除功能
删除功能真的是C++的一个痛,他没有Java的JVM功能所以比java麻烦一点点。但是差不多。等等我会讲述差别。
删除一个单词前我们应该看看这个单词是否存在,如果不存在直接返回。
我们沿路删除单词的时候其实是path减一,如果发现path等于0,那么后面的直接释放,就不需要在取考虑。因为这个就意味着后面的字母只出现一次,并且是这个的单词的字母。
void Trie::Delete_Node(string word)
{
if (!search(word))
return;
int index = 0;
TreeNode *node = root;
for (int i = 0; i < word.length(); i++)
{
index = word[i] - 'a';
if (--node->path == 0)
{
// Java的直接将 node。next[index] = NULL 即可
Delete(node);
node = NULL;
return;
}
node = node->next[index];
}
node->end--;
}
//这个函数java的可以不用写,因为Java有JVM垃圾回收机制。
void Delete(TreeNode *node)
{
if (node == NULL)
return;
for (int num = 0; num < MaxSize; num++)
{
if (node->next[num])
{
Delete(node->next[num]);
node = NULL;
}
}
}
下面是完整代码,需要自取
struct TreeNode;
void Delete(TreeNode *node);
#define MaxSize 26
struct TreeNode
{
TreeNode *next[MaxSize];
int end;
int path;
TreeNode() // 初始化结点
{
end = 0;
path = 0;
for(int i = 0;i< MaxSize;i++)
next[i] = new TreeNode;
}
};
class Trie
{
private:
TreeNode *root;
public:
Trie()
{
root = new TreeNode;
}
void insert_Node(string word);
void Delete_Node(string word);
bool search(string word);
};
void Trie::insert_Node(string word)
{
if (word == "")
return;
int index = 0;
TreeNode *node = root;
for (int i = 0; i < word.length(); i++)
{
index = word[i] - 'a';
if (node)
node = new TreeNode;
node = node->next[index];
node->path++;
}
node->end++;
}
void Trie::Delete_Node(string word)
{
if (!search(word))
return;
int index = 0;
TreeNode *node = root;
for (int i = 0; i < word.length(); i++)
{
index = word[i] - 'a';
if (--node->path == 0)
{
Delete(node);
node = NULL;
return;
}
node = node->next[index];
}
node->end--;
}
void Delete(TreeNode *node)
{
if (node == NULL)
return;
for (int num = 0; num < MaxSize; num++)
{
if (node->next[num])
{
Delete(node->next[num]);
node = NULL;
}
}
}
bool Trie::search(string word)
{
if (word == "")
return false;
TreeNode *node = root;
int index = 0;
for (int i = 0; i < word.length(); i++)
{
index = word[0] - 'a';
if (node->next[index] == NULL)
return false;
node = node->next[index];
}
return true;
}
今天的讲解就到这里,我是i小白,欢迎关注我的公众号。我们一起变强。