字典树==前缀树==Trie树:
查询某个单词(前缀)在所有单词中出现次数的一种数据结构
查询和插入时间复杂度都是O(n),是一种以空间换时间的方法。
字典树基本模板:
#define MAX 26 //字符集大小 typedef struct TrieNode { int nCount; //记录该字符出现次数 struct TrieNode *next[MAX]; }TrieNode; TrieNode Memory[1000000]; int allocp = 0; /*初始化*/ void InitTrieRoot(TrieNode **pRoot) { *pRoot = NULL; } /*创建新结点*/ TrieNode *CreateTrieNode() { int i; TrieNode *p; p = &Memory[allocp++]; p->nCount = 1; for(i = 0 ; i < MAX ; i++) { p->next[i] = NULL; } return p; } /*插入*/ void InsertTrie(TrieNode **pRoot , char *s) { int i , k; TrieNode *p; if(!(p = *pRoot)) { p = *pRoot = CreateTrieNode(); } i = 0; while(s[i]) { k = s[i++] - 'a'; //确定branch if(p->next[k]) p->next[k]->nCount++; else p->next[k] = CreateTrieNode(); p = p->next[k]; } } //查找 int SearchTrie(TrieNode **pRoot , char *s) { TrieNode *p; int i , k; if(!(p = *pRoot)) { return 0; } i = 0; while(s[i]) { k = s[i++] - 'a'; if(p->next[k] == NULL) return 0; p = p->next[k]; } return p->nCount; }
1.统计难题(这里都用数组分配结点,用malloc分配太慢了)
这题就是统计一组字符串中某前缀出现次数(字典树第一类应用),因此只要简单的套模板就行了(在节点中设置一个成员变量nCount,来记录该字符出现次数)
#include <stdio.h> #define MAX 26 typedef struct TrieNode { int nCount; struct TrieNode *next[MAX]; }TrieNode; TrieNode Memory[1000000]; int allocp = 0; void InitTrieRoot(TrieNode **pRoot) { *pRoot = NULL; } TrieNode *CreateTrieNode() { int i; TrieNode *p; p = &Memory[allocp++]; p->nCount = 1; for(i = 0 ; i < MAX ; i++) { p->next[i] = NULL; } return p; } void InsertTrie(TrieNode **pRoot , char *s) { int i , k; TrieNode *p; if(!(p = *pRoot)) { p = *pRoot = CreateTrieNode(); } i = 0; while(s[i]) { k = s[i++] - 'a'; //确定branch if(p->next[k]) p->next[k]->nCount++; else p->next[k] = CreateTrieNode(); p = p->next[k]; } } int SearchTrie(TrieNode **pRoot , char *s) { TrieNode *p; int i , k; if(!(p = *pRoot)) { return 0; } i = 0; while(s[i]) { k = s[i++] - 'a'; if(p->next[k] == NULL) return 0; p = p->next[k]; } return p->nCount; } int main(void) { char s[11]; TrieNode *Root = NULL; InitTrieRoot(&Root); while(gets(s) && s[0]) { InsertTrie(&Root , s); } while(gets(s)) { printf("%d\n", SearchTrie(&Root , s)); } return 0; }
2.Phone List
这道题就是判断一组字符串中是否有一个字符串是另一个字符串的前缀(字典树第二类应用)。
分析:我们只要在结点中添加一个nEndFlag成员变量即可。若nEndFlag == 1,说明该结点字符是某一字符串的结尾(假设为A),若在插入B字符串的过程中经过这一结点,则说明A是B的前缀;还有一种情况,当要插入最后一个字符c时,却发现p->next[c-'0']为真,则说明该字符串是一个前缀字符串,eg:先插入abcde,再插入abc这种情况
#include <stdio.h> #define MAX 10 typedef struct TrieNode { int nEndFlag; //标记该字符是否是某一字符串的结尾 struct TrieNode *next[MAX]; }TrieNode; TrieNode Memory[1000000]; int allocp = 0 , nFlag = 0; void InitTrieRoot(TrieNode **pRoot) { *pRoot = NULL; } TrieNode *CreateTrieNode() { int i; TrieNode *p; p = &Memory[allocp++]; p->nEndFlag = 0; for(i = 0 ; i < MAX ; i++) { p->next[i] = NULL; } return p; } void InsertTrie(TrieNode **pRoot , char *s) { int i , k; TrieNode *p; if(!(p = *pRoot)) { p = *pRoot = CreateTrieNode(); } i = 0; while(s[i]) { k = s[i++] - '0'; if(p->next[k]) { if(p->next[k]->nEndFlag == 1 || s[i] == '\0') //先短后长 || 先长后短 { nFlag = 1; return; } } else { p->next[k] = CreateTrieNode(); } p = p->next[k]; } p->nEndFlag = 1; //标记结尾 } int main(void) { int z , n , i; char s[11]; TrieNode *Root; scanf("%d", &z); while(z-- > 0) { nFlag = allocp = 0; InitTrieRoot(&Root); scanf("%d" , &n); getchar(); for(i = 0 ; i < n ; i++) { gets(s); if(!nFlag) InsertTrie(&Root , s); } nFlag ? printf("NO\n") : printf("YES\n"); } return 0; }