Trie树:
http://hihocoder.com/problemset/problem/1014?sid=1526165
描述
小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。
这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能对于每一个我给出的字符串,都在这个词典里面找到以这个字符串开头的所有单词呢?”
身经百战的小Ho答道:“怎么会不能呢!你每给我一个字符串,我就依次遍历词典里的所有单词,检查你给我的字符串是不是这个单词的前缀不就是了?”
小Hi笑道:“你啊,还是太年轻了!~假设这本词典里有10万个单词,我询问你一万次,你得要算到哪年哪月去?”
小Ho低头算了一算,看着那一堆堆的0,顿时感觉自己这辈子都要花在上面了...
小Hi看着小Ho的囧样,也是继续笑道:“让我来提高一下你的知识水平吧~你知道树这样一种数据结构么?”
小Ho想了想,说道:“知道~它是一种基础的数据结构,就像这里说的一样!”
小Hi满意的点了点头,说道:“那你知道我怎么样用一棵树来表示整个词典么?”
小Ho摇摇头表示自己不清楚。
“你看,我们现在得到了这样一棵树,那么你看,如果我给你一个字符串ap,你要怎么找到所有以ap开头的单词呢?”小Hi又开始考校小Ho。
“唔...一个个遍历所有的单词?”小Ho还是不忘自己最开始提出来的算法。
“笨!这棵树难道就白构建了!”小Hi教训完小Ho,继续道:“看好了!”
“那么现在!赶紧去用代码实现吧!”小Hi如是说道
网上说这个字典树的太多了,不说了。
需要注意的地方
1. 如果当前字符不在节点(如root节点)的子节点列表里,新建节点插入作为 root节点子节点后,记得将root置为新建节点,后续插入是在以新节点为根节点的树上插入的。对应到代码中就是:
if ( j == root->childs.end() ) {//如果当前字符在子节点列表不存在,则新建一个节点作为当前父节点的子节点,并从该子树开始插入
TrieNode* trieNode = new TrieNode( str[i] );
trieNode->count++;
root->childs.push_back(trieNode);
root = trieNode;
}
2.如果在for循环中将迭代器所使用的容器改变了,会出错的,如
list<TrieNode>::iterator j = root.childs.begin();
for ( ; j != root.childs.end(); ++j ) {
if ( j->character == str[i] ) {//如果当前字符在某个子节点存在
root = *j;,
break;
}
//error: list iterators incompatible
//出错,无法比较
if( j==root.childs.end() ){
}
3. 你也可以参考这篇文章编写代码,但需要注意的是,这里
node *next[26]; //定义的字典树为只有26个小写字母,可增加减少
因为26个字母和数字一一对应,所以可以直接采用这种方式保存子节点,子节点字符就是索引对应的字符。
但是,如果题目说,这里不一定是26个字母,也可能是别的符号,那么这种方法就不好使用了。
4.搜索,插入,能不能使用递归?当然可以了,明显,子问题和大问题相同,只要改变root,还有字符串就行。
比如把abc,已经插入了a,那么下一次就是插入字符串bc。
// Trie树.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <list>
#include <map>
#include <string>
using namespace std;
class TrieNode
{
public:
TrieNode( char character );
~TrieNode();
list<TrieNode*> childs;
char character;
void insert(TrieNode* trieNode, string str);
TrieNode* find(TrieNode* trieNode, string str);
int count;
private:
};
//子节点与保存的字符起始状态都为空
TrieNode::TrieNode( char character = NULL )
:character(character),count(0)
{
}
TrieNode::~TrieNode()
{
}
void TrieNode::insert(TrieNode* root, string str)
{
for (int i = 0; i < str.size(); ++i) {//依次插入字符串的每个字符
if ( root->childs.size() == 0 ) {//父节点无子节点,直接新建节点插入
TrieNode* trieNode = new TrieNode( str[i] );
trieNode->count++;
root->childs.push_back(trieNode);
root = trieNode;
}else {
//遍历子节点列表,查看当前字符是否与某子节点字符相同,相同则沿着该子节点继续搜索
list<TrieNode*>::iterator j = root->childs.begin();
for ( ; j != root->childs.end(); ++j ) {
if ( (*j)->character == str[i] ) {//如果当前字符在某个子节点存在
break;
}
}
if ( j == root->childs.end() ) {//如果当前字符在子节点列表不存在,则新建一个节点作为当前父节点的子节点,并从该子树开始插入
TrieNode* trieNode = new TrieNode( str[i] );
trieNode->count++;
root->childs.push_back(trieNode);
root = trieNode;
}
else {//如果当前字符在子节点列表里
root = *j;
(*j)->count++;
}
}
}
}
//查找字符串对应的叶子节点
TrieNode* TrieNode::find(TrieNode* root, string str)
{
for (int i = 0; i < str.size(); ++i) {//依次查找字符串的每个字符
if (root->childs.size() == 0) {//父节点无子节点
return NULL;
}
else {
//遍历子节点列表,查看当前字符是否与某子节点字符相同,相同则沿着该子节点继续搜索
list<TrieNode*>::iterator j;
for (j = root->childs.begin(); j != root->childs.end(); ++j) {
if ( (*j)->character == str[i]) {//如果当前字符在某个子节点存在,在该子树上继续查找
break;
}
}
if (j == root->childs.end()) {//如果当前字符在子节点列表不存
return NULL;
}
else {
root = *j;
}
}
}
return root;
}
int main()
{
int inputStringTimes = 0;
cin >> inputStringTimes;
TrieNode* trie = new TrieNode();
list<string> wordList;
while ( inputStringTimes>0 )
{
inputStringTimes--;
string str;
cin >> str;
wordList.push_back(str);
trie->insert(trie, str );
}
int checkTimes = 0;
cin >> checkTimes;
while (checkTimes > 0)
{
checkTimes--;
string str;
cin >> str;
TrieNode* t = trie->find(trie, str);
if ( NULL != t )
{
cout << t->count << endl;
}
else {
cout << 0 << endl;
}
}
}
很久,估计两年左右没做过题了,纪念下。