假设给定一个关键词rob,如果某一个单词中包含此字符串,那么就断定此字符串为bad word。如problem就包含rob,那么它就是bad word。其实这种叙述是:“判断一个字符串是否是另一个字符串的子串”的另外一种描述。熟悉字符串匹配的人都知道,这个问题可以用KMP算法很快就能够解决。但是就像我们不嫌弃自己的钱多的一样,方法多了,路子就多了。所以决定应用后缀trie来解决这个问题。后缀trie是trie树的一种变种形式。给定字符串S=s1s2s3s4....sn,把si...sn称作S的一个后缀,其中 1<=i<=n,按照次序,我们把S的每一个后缀都插入到一个trie树中,最终形成的树,就叫S的后缀trie树。对于单词problem的后缀依次为:
problem, roblem, oblem, blem, lem, em , m.给出这个单词的后缀trie:
建立好了后缀trie便可以清楚的看到,在树中寻找rob是相当容易的事情,只需要3次比对,也就是length of “rob”的长度。可以说是如果trie建立好以后,寻找什么都是很快的。但是在建立后缀trie的时候却要耗费很大的空间和很长的时间,这就是为什么后缀trie没有被人们所亲睐的原因。仔细看图,体会一下它的奇妙之处吧,它涵盖了problem中以任何字母开头,在一定长度内的任何子串。下面给出代码,它的时间和空间复杂度都在O(n^2).
#include<stdio.h> #include<stdlib.h> #include<string.h> #define CHILD_NUM 26 typedef struct trie_node{ char key; struct trie_node *child[CHILD_NUM]; }TrieNode, *TrieTree; TrieNode *create_node(char key) { TrieNode *temp = (TrieNode*)malloc(sizeof(TrieNode)); temp->key = key; for(int i = 0; i < CHILD_NUM; i++) { temp->child[i] = NULL; } return temp; } void insert_trie(TrieTree T, char *p_word) { TrieNode *p = T; while(*p_word) { if(p->child[*p_word - 'a'] == NULL) { p->child[*p_word - 'a'] = create_node(*p_word); } p = p->child[*p_word - 'a']; p_word++; } } int search_trie(TrieTree T, char *p_word) { TrieNode *p = T; while(p && *p_word) { if(p->child[*p_word - 'a'] == NULL) { return 0; } else { p = p->child[*p_word - 'a']; p_word++; } } if(p == NULL && *p_word != '\0') { return 0; } else { return 1; } } void main() { TrieTree T = create_node(' '); char word[] = "problem"; char *p = word; char bad_word[] = "rob"; int len = strlen(word); int i; for(i = 0; i < len; i++) { insert_trie(T, p + i); } if(search_trie(T, bad_word)) { printf("this word is bad word!\n"); } else { printf("this word is good word!\n"); } }