数据结构与算法之字典树解题
字典树,又称标定搜索树,是一种树形结构,也是一种哈希树的变形,典型的应用包括统计,排序和保存大量的字符串,但又不局限于字符串,还可以是数字等,所以常被搜索引擎系统用来进行词频统计,如搜索网站可用字典树进行热门搜索词的统计。它的特点是:利用字符串的公共前辍来节约存储空间,最大限度的减小字符串的比较,查询效率比哈希表高。对于大量数据而言,字典树所需要的空间相对较大,但是对于查询某个单词而言,其时间复杂度为O(n), n为字符串的长度,对于大量字符串而言,这样的查找速度是相当可观的。
字典树的树形图为:
线性结构为:
其中第一节点为根节点,不存储数据。当查询某个单词时,从根节点开始,向下遍历,如要查找hi,则h->i,时间复杂度为O(2), 如果用常规查找,即从头开始遍历,需要O(15)即所有字符串的总长度,这样的对于大量数据而言,查找效率是不现实的。 如果要查找一个不存在的字符串如kkk, 当遍历发现不存在以k开头的字符串,直接跳出,所以说查询效率是非常高的。
同时可以将大量字符数据保存到字典树中,利用字典树的公共前辍,可以大幅度地节约空间,即在字典树的一个分枝中可以保存大量有相同前辍的字符。
字典树的实现:
next表示每层有多少种类的数,如果为小写字母为26,大小写为52等等
v表示一个字典树有多少公共的前辍,这个可以根据程序需要而改变。
1 #define MAX 26 2 typedef struct Trie 3 { 4 Trie *next[MAX]; 5 int v; //根据需要变化 6 Trie() { //初始化节点的next的各个节点为NULL 7 for (int i = 0; i < MAX; i++) 8 next[i] = NULL; 9 } 10 }; 11 12 Trie *root = new Trie; 13 //创建字典树 14 void createTrie(char *str) 15 { 16 int len = strlen(str); 17 Trie *p = root, *q; 18 for(int i=0; i<len; ++i) 19 { 20 int id = str[i]-'0'; 21 if(p->next[id] == NULL) 22 { 23 q = new Trie; 24 q->v = 1; //初始v==1 25 for(int j=0; j<MAX; ++j) 26 q->next[j] = NULL; 27 p->next[id] = q; 28 p = p->next[id]; 29 } 30 else 31 { 32 p->next[id]->v++; 33 p = p->next[id]; 34 } 35 } 36 p->v = -1; //若为结尾,则将v改成-1表示 37 } 38 //在字典树中查找字符串 39 int findTrie(char *str) 40 { 41 int len = strlen(str); 42 Trie *p = root; 43 for(int i=0; i<len; ++i) 44 { 45 int id = str[i]-'0'; 46 p = p->next[id]; 47 if(p == NULL) //若为空集,表示不存以此为前缀的串 48 return 0; 49 if(p->v == -1) //字符集中已有串是此串的前缀 50 return -1; 51 } 52 return -1; //此串是字符集中某串的前缀 53 } 54 //对于上述创建的字典树,有时会超内存,所以要释放内存 55 int dealTrie(Trie* T) 56 { 57 int i; 58 if(T==NULL) 59 return 0; 60 for(i=0;i<MAX;i++) 61 { 62 if(T->next[i]!=NULL) 63 deal(T->next[i]); 64 } 65 delete T; 66 return 0; 67 }
字典树的查找过程:
(1)每次从根节点开始一次检索;
(2)在取得字符串的第一个关键字后,根据该关键字选择对应的子树并转到该子树继续进行检索。
(3)在子树上找到第二个关键字后,并进行进一步在相应子树上进行检索。
(4)迭代过程......(若遇到找不到的关键字即结束查找,或者按需要创建节点)
(4)在某个节点上,字符串的所有关键字均找到,读取该节点上的附加信息,即完成查找。
相关使用题目:
1001: 土豪我们做朋友吧!
Time Limit: 1 Sec Memory Limit: 16 MBSubmit: 149 Solved: 27
[Submit][Status][Web Board]
Description
Water最近掌握了一大波土豪的电话,他决定一个个打电话去找土豪做朋友。可是土豪们不堪骚扰,于是联合通信公司对Water的电话做了一个封杀规则。
每当Water打出一个电话后,该电话号码以及以该电话号码开始的号码都永久无法被Water打出。
举个栗子,Water打出了号码12345,不管有没人接电话,下一次拨号时12345及12345*(类似1234567)的号码都无法拨出,只能听到“您拨打的用户不接你电话,请不要再拨”。
但是如果Water先拨打了1234567,下一次若为12345还是可以打出的。
Water是个锲而不舍的人,他会把他手头上的电话列表按列表顺序都打一次。
Input
第一行为N [1,1 000 000]。
接下来N行是N个土豪的电话。电话为长度在[1,100]的数字序列。
Output
一个整数M,代表Water成功骚扰到的土豪的个数。(即没有被封杀的电话个数)
Sample Input
5
123
12
12345
123
23451
Sample Output
3
HINT
实现代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 const int MAX = 10; 6 struct Node { 7 int v; 8 Node* next[MAX];//下一层字典 9 Node() { 10 for (int i = 0; i < MAX; i++) 11 next[i] = NULL; 12 } 13 }; 14 Node* root = new Node; 15 void trieTree (char* str, int& number) { 16 int len = strlen(str); 17 bool flag = true; 18 Node* p = root, *q;//每次都从字典树的根节点开始遍历 19 for (int i = 0; i < len; i++) { 20 int location = str[i]-'0';//可以将字符串中的数字转换为int型的数字 21 if (p->next[location] == NULL) { 22 q = new Node; 23 q->v = 1; 24 p->next[location] = q; 25 p = p->next[location]; 26 } else { 27 if (p->next[location]->v == -1) {//如果经过了该已经输入的最短号码的最后一位,则说明该输入的号码不能打通,即令flag=false 28 flag = false; 29 p = p->next[location];//这步很关键,因为会关系到35行的赋值,即标示最短号码的最后一位为-1 30 break;//跳出循环,不将该电话号码存入,节省内存,因为改题只需要判断某个电话号码可不可以打通 31 } 32 p = p->next[location]; 33 } 34 } 35 p->v = -1; 36 if (flag == true)//如果未经过最短号码,则说明不是与最短号码有共同前辍的,可以打通 37 number++; 38 } 39 int main() { 40 int phones; 41 char* phone; 42 int number = 0; 43 phone = new char[101]; 44 scanf("%d", &phones); 45 for (int i = 0; i < phones; i++) { 46 scanf("%s", phone); 47 trieTree(phone, number); 48 } 49 printf("%d\n", number); 50 return 0; 51 } 52
相关题目链接:
统计难题:http://acm.hdu.edu.cn/showproblem.php?pid=1251
Phone List: http://acm.hdu.edu.cn/showproblem.php?pid=1671
字典树资料:算法合集之《浅析字母树在信息学竞赛中的应用》
http://www.rayfile.com/zh-cn/files/cb23e411-c735-11df-934c-0015c55db73d/
参考博客:http://www.cnblogs.com/tanky_woo/archive/2010/09/24/1833717.html