字典树学习小结

来源:互联网 发布:linux vsftpd 启动 编辑:程序博客网 时间:2024/05/20 19:33

写了几道字典树的基础题了,现在写一个总结吧。


其实动态建树和静态建树都一样,只是动态建树省空间费时间,静态建树省时间费空间。

那么如果碰上数据很大的题的时候,还是选择静态建树吧,毕竟内存没有那么容易超。


那下面先说动态建树吧:

变量:

struct Trie{Trie *next[10];int v;void init(){v = 0;for (int i = 0 ; i <= 9 ; i++)next[i] = NULL;}};Trie *root;


next[] 指针数组指向下一个节点。数组大小根据题目改变,例如,题目要求只有小写字母,那么开26就行了;如果包括大写,那么开52,如果还有数字,那就是62(一般包括数字的话,个人感觉树的规模不会建的太大,要不就出事了)。

变量v的含义非常灵活,可以表示当前字母被共享使用了多少次,也可以变成bool型表示一个单词的结束。这个要就题论题,不能太拘泥模板。

*root 表示指向根节点的指针,每次查询要先从根节点(不包括任何字母)开始。


初始化:

root = (Trie *)malloc(sizeof(Trie));root->init();


第一行代码给 *root 指向的结构体开辟空间,第二行初始化root。


插入字符串:

void insert(char *s)//字符串 {int l = strlen(s);Trie *p = root , *q;for (int i = 0 ; i < l ; i++){int id = idx(s[i]);if (p->next[id] == NULL){q = (Trie *)malloc(sizeof(Trie));//开新指针 q->init();p->next[id] = q;}p = p->next[id];p->v++;}}


思路大概就是,从字符串中依次取字符,如果树的枝叶有这个字符,那么就顺着这个枝叶对下一个字符进行操作,如果不存在,就再开一个叶子存这个字母。

注意:我这里的v的意思是这个位置的字母被多少字符串共享,其他使用要就题来改变。


查找操作:

bool find(char *s){int l = strlen(s);Trie *p = &tree[0];for (int i = 0 ; i < l ; i++){int id = idx(s[i]);if (p->next[id] == NULL)//没有这个单词 return false;p = p->next[id];}if (p->word)return true;return false;//如果有些单词只是 "路过" 也不算 }


PS:这时候的word变量代替了之前的变量v,表示这个位置是否形成了单词。

这个函数的用法是查询一个单词是否出现过。如果插入操作看懂了,这个应该不难,就不细讲了。


查找最小前缀并输出:

void min_permix(char s[][22],int x)//查询最小前缀 {printf ("%s ",s[x]);int l = strlen(s[x]);Trie *p = root;for (int i = 0 ; i < l ; i++){printf ("%c",s[x][i]);int id = idx(s[x][i]);p = p->next[id];if (p->v == 1)//如果字母单独出现说明已经出现最小前缀了,结束break;}printf ("\n");}

这个也应该不难理解了。



释放内存:(有些题需要把结构体开辟的内存释放掉,要不会MLE)

void del(Trie *root)//不释放内存的话会MLE {for (int i = 0 ; i < 10 ; i++){if (root->next[i] != NULL)del(root->next[i]);}free(root);}

使用free函数+dfs释放内存。





然后说一下静态储存:

struct Trie{Trie *next[10];int v;void init(){v = 0;for (int i = 0 ; i <= 9 ; i++)next[i] = NULL;}}tree[1<<16];int ant;

这个时候要把tree开的足够大,把你这棵树可能出现的最大枝叶树全部包括。

ant这个变量是动态的,不断往后开新的结构体来储存枝叶。(这一个不懂可以看一下下面的操作)(使用前记得初始化为1,而把tree[ 0 ] 作为根节点)



插入操作:

void insert(char *s){int l = strlen(s);Trie *p = &tree[0], *q;//tree[0]为根节点 for (int i = 0 ; i < l ; i++){int id = idx(s[i]);if (p->next[id] == NULL){q = &tree[ant++];p->next[id] = q;}p = p->next[id];}p->v = -1;//这个-1表示一个字符串已经结束 }


然后这个插入操作就不难理解了,其他操作类似动态的字典树,就不再一一码了。



然后贴一个大代码:

动态:(查找最小前缀)

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;#define CLR(a,b) memset(a,b,sizeof(a))#define INF 0x3f3f3f3fstruct Trie{Trie *next[26];int v;void init(){v = 0;for (int i = 0 ; i < 26 ; i++)next[i] = NULL;}};Trie *root;int idx(char c){return c - 'a';}void insert(char s[][22],int x){int l = strlen(s[x]);Trie *p = root , *q;for (int i = 0 ; i < l ; i++){int id = idx(s[x][i]);if (p->next[id] == NULL){q = (Trie *)malloc(sizeof(Trie));//开新指针 q->init();p->next[id] = q;}p = p->next[id];p->v++;}}void min_permix(char s[][22],int x)//查询最小前缀 {printf ("%s ",s[x]);int l = strlen(s[x]);Trie *p = root;for (int i = 0 ; i < l ; i++){printf ("%c",s[x][i]);int id = idx(s[x][i]);p = p->next[id];if (p->v == 1)//如果字母单独出现说明已经出现最小前缀了,结束break;}printf ("\n");}int main(){char s[1011][22];int ant = 0;root = (Trie *)malloc(sizeof(Trie));root->init();while (~scanf ("%s",s[ant++]))insert(s,ant-1);for (int i = 0 ; i < ant ; i++)min_permix(s , i);return 0;}

静态:(查找某个字符串是否可以由已知的两个字符串拼接而成)

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;#define CLR(a,b) memset(a,b,sizeof(a))#define INF 0x3f3f3f3f#define idx(x) (x-'a')struct Trie{Trie *next[26];bool word;void clear(){for (int i = 0 ; i < 26 ; i++)next[i] = NULL;word = false;}}tree[100000];int ant;void insert(char *s){int l = strlen(s);Trie *p = &tree[0] , *q;for (int i = 0 ; i < l ; i++){int id = idx(s[i]);if (p->next[id] == NULL){q = &tree[ant++];q->clear();p->next[id] = q;}p = p->next[id];}p->word = true;}bool find(char *s){int l = strlen(s);Trie *p = &tree[0];for (int i = 0 ; i < l ; i++){int id = idx(s[i]);if (p->next[id] == NULL)//没有这个单词 return false;p = p->next[id];}if (p->word)return true;return false;//如果有些单词只是 "路过" 也不算 }int main(){ant = 1;tree[0].clear();char str[50011][30];int num = 0;while (~scanf ("%s",str[num++]))insert(str[num-1]);for (int i = 0 ; i < num ; i++)//从第一个单词开始拆{int l = strlen(str[i]);for (int j = 1 ; j < l ; j++){char a[30];strncpy(a,str[i],j);a[j] = '\0';//strncpy函数不添加'\0' char b[30];strcpy(b,str[i]+j);if (find(a) && find(b)){printf ("%s\n",str[i]);break;//输出完此轮不用再搜了 }}}return 0;}




有错误欢迎留言指出。

如果感觉写的不错,转载请表明出处链接哦~






0 0
原创粉丝点击