Trie树
Trie树也称字典树,因为其效率很高,所以在在字符串查找、前缀匹配等中应用很广泛,其高效率是以空间为代价的。
一.Trie树的原理
利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。
下面以英文单词构建的字典树为例,这棵Trie树中每个结点包括26个孩子结点,因为总共有26个英文字母(假设单词都是小写字母组成)。
则可声明包含Trie树的结点信息的结构体:
- typedef struct TrieNode
- {
- bool isStr; //标记该节点是否构成单词
- struct TrieNode *next[Max];
- TrieNode():isStr(false)
- {
- memset(next, NULL, sizeof(next)); //将next内存全部置为NULL
- }
- };
特别注意:在节点构造结构体中,对每个节点的包含信息进行初始化,是很重要的,为后续public函数中额判断做好基础。
其中next是一个指针数组,存放着指向各个孩子结点的指针。isStr 为标记一个串结束存在的标志。
如给出字符串"abc","ab","bd","dda",根据该字符串序列构建一棵Trie树。则构建的树如下:
Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
但是却是以空间为代价的,比如图中每个结点所占的空间都为(26*4+1)Byte=105Byte,那么这棵Trie树所占的空间则为105*8Byte=840Byte,而普通的逐个查找所占空间只需(3+2+2+3)Byte=10Byte。
二.Trie树的操作
在Trie树中主要有3个操作,插入、查找和删除。一般情况下Trie树中很少存在删除单独某个结点的情况,因此只考虑删除整棵树。
1.插入
假设存在字符串str,Trie树的根结点为root。i=0,p=root。
1)取str[i],判断p->next[str[i]-97]是否为空,若为空,则建立结点temp,并将p->next[str[i]-97]指向temp,然后p指向temp;
若不为空,则p=p->next[str[i]-97];
2)i++,继续取str[i],循环1)中的操作,直到遇到结束符'\0',此时将当前结点p中的isStr置为true。
2.查找
假设要查找的字符串为str,Trie树的根结点为root,i=0,p=root
1)取str[i],判断判断p->next[str[i]-97]是否为空,若为空,则返回false;若不为空,则p=p->next[str[i]-97],继续取字符。
2)重复1)中的操作直到遇到结束符'\0',若当前结点p不为空并且isStr为true,则返回true,否则返回false。
3.删除
删除可以以递归的形式进行删除。
代码:
- /* Arby_Trie树 2012.10.04 8:04am*/
- #include "iostream"
- using namespace std;
- #define Max 26
- class Trie
- {
- private:
- typedef struct TrieNode
- {
- bool isStr; //标记该节点是否构成单词
- struct TrieNode *next[Max];
- TrieNode():isStr(false)
- {
- memset(next, NULL, sizeof(next)); //将next内存全部置为NULL
- }
- };
- TrieNode *root;
- public:
- Trie();
- ~Trie();
- void insert(const char *s);
- void insert(TrieNode *root, const char *s);
- int search(const char *s);
- int search(TrieNode *root, const char *s);
- void del();
- void del(TrieNode *root);
- };
- Trie::Trie()
- {
- root = new TrieNode();
- }
- Trie::~Trie()
- {
- }
- void Trie::insert(const char *s)
- {
- insert(root, s);
- }
- void Trie::insert(TrieNode *root, const char *s)
- {
- int i = 0;
- TrieNode *p = root;
- while(s[i] != '\0')
- {
- if(p->next[s[i] - 'a'] == NULL)
- {
- TrieNode *temp = new TrieNode();
- p->next[s[i] - 'a'] = temp;
- p = p->next[s[i] - 'a'];
- }
- else
- {
- p = p->next[s[i] - 'a'];
- }
- i++;
- }
- p->isStr = true; //到达尾部,标记一个串
- }
- int Trie::search(const char *s)
- {
- return search(root, s);
- }
- int Trie::search(TrieNode *root, const char *s)
- {
- if(root == NULL)
- return false;
- TrieNode *p = root;
- int i = 0;
- while(s[i] != '\0')
- {
- if(p->next[s[i] - 'a'] == NULL)
- return false;
- else
- {
- p = p->next[s[i] - 'a'];
- }
- i++;
- }
- return (p != NULL && p->isStr == true);
- }
- void Trie::del()
- {
- del(root);
- }
- void Trie::del(Trie::TrieNode *root)
- {
- if(root->isStr == false)
- for(int i = 0; i < Max; i++)
- {
- if(root->next[i] != NULL)
- del(root->next[i]);
- }
- delete root;
- }
- //simple测试
- int main()
- {
- Trie TrieTree;
- char *s = "hello";
- char *s1 = "aa";
- TrieTree.insert(s);
- TrieTree.insert(s1);
- int test = TrieTree.search(s);
- cout << test << endl;
- test = TrieTree.search("bb");
- cout << test << endl;
- TrieTree.del();
- return 0;
- }
Trie树的练习,poj3630题
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 17042 | Accepted: 5434 |
Description
Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogue listed these numbers:
- Emergency 911
- Alice 97 625 999
- Bob 91 12 54 26
In this case, it's not possible to call Bob, because the central would direct your call to the emergency line as soon as you had dialled the first three digits of Bob's phone number. So this list would not be consistent.
Input
The first line of input gives a single integer, 1 ≤ t ≤ 40, the number of test cases. Each test case starts with n, the number of phone numbers, on a separate line, 1 ≤ n ≤ 10000. Then follows nlines with one unique phone number on each line. A phone number is a sequence of at most ten digits.
Output
For each test case, output "YES" if the list is consistent, or "NO" otherwise.
Sample Input
2 3 911 97625999 91125426 5 113 12340 123440 12345 98346
Sample Output
NO YES
题目大意:查找相同的前缀,但是不能是已经完结的输入号码,正好用字典树进行insert检查:
- #include "iostream"
- #include "windows.h"
- using namespace std;
- #define Max 10
- int nodeNum;
- int cases, count;
- typedef struct TrieNode
- {
- bool isStr; //标记该节点是否构成单词
- TrieNode *next[Max]; //每个节点存储所要的一个号码串
- //TrieNode():isStr(false)
- //{
- // memset(next, NULL, sizeof(next)); //将next内存全部置为NULL
- //}
- };
- TrieNode node[10000];
- class Trie
- {
- private:
- TrieNode root;
- public:
- Trie();
- ~Trie();
- bool insert(char num[]);
- };
- Trie::Trie()
- {
- root = node[0];
- }
- Trie::~Trie()
- {
- }
- bool Trie::insert(char num[])
- {
- int i = 0;
- TrieNode *p = &root;
- int len = strlen(num);
- while(num[i])
- {
- if(i == len-1 && p->next[num[i] - '0'] != NULL)
- {
- return false;
- }
- if(p->next[num[i] - '0'] == NULL) //如果不存在的话,则建立新的节点
- {
- p->next[num[i] - '0'] = &node[nodeNum];
- node[nodeNum].isStr = false; //初始化
- memset(node[nodeNum].next,NULL,sizeof(node[nodeNum].next)); //这个很重要
- nodeNum++;
- }
- if(p->next[num[i] - '0']->isStr == true) //到达一串数字的结尾了
- {
- return false;
- }
- p = p->next[num[i] - '0'];
- i++;
- }
- p->isStr = true; //到达尾部,标记一个串
- return true;
- }
- //simple测试
- int main()
- {
- cin >> cases;
- while(cases--)
- {
- nodeNum = 1;
- bool flage = true;
- cin >> count;
- cin.get();
- char tel[11];
- Trie t;
- while(count--)
- {
- cin.get(tel, 11).get();
- //cin.get();
- if(!t.insert(tel))
- {
- flage = false;
- }
- }
- if(flage)
- cout << "YES" <<endl;
- else
- cout << "NO" << endl;
- }
- return 0;
- }
存在问题 : 结构体创建如果放在类内,需要建一个构造函数,对结构体成员初始化,但是这样用的内存为栈内存,比较小,而我这次需要的是100000的结构体数组,这样的话,还未运行栈已经溢出了,因此需要将结构体在类外定义,并且结构体数组变为全局变量,这样就可以进行很好堆内存使用了。
遗留问题:将结构体定义在类内,将数组变为static,这样进行实现,希望有志之士指点。