假设给定一个关键词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");
}
}