标签: acm算法数据结构c语言
2014-10-23 10:36
315人阅读
收藏
举报
/*
再次声明本文为转载
原作者地址
http://blog.csdn.net/u013548531/article/details/38896059
*/
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251
字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
说到底,其实不难算是算法应当归为数据结构,一种特殊的树。
字典树有三个特点,非常重要,也是构建字典树的三个标准原则吧。
1.根节点为空
2.树的每一层的元素独一无二不可重复
3.从根节点到某一节点的路径就是该节点对应的字符串。
比如说这个样例
banana
band
bee
absolute
acm
这五个单词组成的字典树应该如下图所示:
(中间有些单词很长省略了..)这个图应该很直观了。
字典树的操作(看资料一般定义了四个操作。创建 添加 删除 查找。由于删除操作不常用于是在这就不说了):
1.创建
创建应该分成两个部分。定义结点和建立树。
定义结点的代码如下:
- struct Trie
- {
- int num;
- struct Trie *next[maxn];
- };
1.num:到这个节点,前缀相同的数目,这个地方看起来可能并不理解,那么继续往下看会明白。(PS:这个num随题目变化而不同)
2.next是一个指针数组,其中maxn是每一层中最多的元素数。比如说这个题目当中,每一层最多只有26个小写字母,maxn = 26.
***建立字典树:
这个是最重要的部分。
建立字典树首先要创建一个Trie类型的指针root,并且将其初始化(next[i] = NULL)。
- struct Trie *root = (struct Trie*)malloc(sizeof(struct Trie));
-
- void initialize()
- {
-
- root->num = 0;
- for(int i = 0 ; i < maxn ; ++i)
- root->next[i] = NULL;
- }
反应到图上就如下图所示。
(PS:用c语言的话千万别忘了用malloc函数)
建立了树根之后,开始完善这颗树,注意一定要遵守那三个原则。
先给出代码,然后以acm这个单词为例从图上体现一下。
- void insert(char *str)
- {
- int len = strlen(str);
- struct Trie *p = root;
-
- for(int i = 0 ; i < len ; ++i)
- {
- int pos = str[i] - 'a';
- if(p->next[pos] == NULL)
- {
-
- struct Trie *temp = (struct Trie*)malloc(sizeof(struct Trie));
- temp->num = 1;
- for(int j = 0 ; j < maxn ; ++j)
- temp->next[j] = NULL;
- p->next[pos] = temp;
- p = p->next[pos];
- }
- else
- {
- p = p->next[pos];
- p->num++;
- }
- }
- }
我们把执行过程画出来。
(假设str == acm)
在循环开始之前,我们得到的是这样的图
for(int i = 0 ; i < len ; ++i)
循环开始,第一个字符是a
pos = 0
显然p->next[pos] = NULL,则建立一个新的结点,然后p->next[pos]指向这个结点,如下图:
然后再把temp赋值给p使得temp称为新的p
(注意temp的num值赋值为1了,表示到现在为止有一个单词的开头字母是a,如果你在加入添加单词absolute,那么是指向else里边的代码,此时a这一点的num值为2,表示到现在为止有两个单词的开头字是a。这句话对于理解num的意义很有必要,后续的查找代码也更容易看懂)
然后不断的这样构造即可。
2.查找
同样,先贴代码然后解释。
- int find(char *str)
- {
- struct Trie *p = root;
- int len = strlen(str);
- for(int i = 0 ; i < len ; ++i)
- {
- int pos = str[i] - 'a';
- p = p->next[pos];
- if(p == NULL)
- return 0;
- }
- return p->num;
- }
在看懂了上边的图和理解了num的意思之后,这段代码理解起来应该就很容易了。
假设字典树已经建立完毕,对于ac这组数据,我们寻找到底有几个单词的前缀是ac。
str == ac
循环:
i = 0时 pos = a - 'a' = 0;
p - > next[0]不是空,继续到下一层。
i = 1时 pos = c - 'a' = 2;
p - > next[2]不是空,继续下一层。
i = 2 循环结束。
返回当前结点的num(也就是到该结点的字符串刚好为ac的单词的个数)
(如果想更加详细的了解的话..多画图,多找几个例子,顺着代码走一遍,就明白了了)
附AC代码,98MS
- </pre><pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:14px;">#include <stdio.h>
- #include <string.h>
- #include <malloc.h>
- #define maxn 26
- struct Trie
- {
- int num;
- struct Trie *next[maxn];
- };
-
- struct Trie *root = (struct Trie*)malloc(sizeof(struct Trie));
-
- void initialize()
- {
-
- root->num = 0;
- for(int i = 0 ; i < maxn ; ++i)
- root->next[i] = NULL;
- }
- void insert(char *str)
- {
- int len = strlen(str);
- struct Trie *p = root;
-
- for(int i = 0 ; i < len ; ++i)
- {
- int pos = str[i] - 'a';
- if(p->next[pos] == NULL)
- {
-
- struct Trie *temp = (struct Trie*)malloc(sizeof(struct Trie));
- temp->num = 1;
- for(int j = 0 ; j < maxn ; ++j)
- temp->next[j] = NULL;
- p->next[pos] = temp;
- p = p->next[pos];
- }
- else
- {
- p = p->next[pos];
- p->num++;
- }
- }
- }
- int find(char *str)
- {
- struct Trie *p = root;
- int len = strlen(str);
- for(int i = 0 ; i < len ; ++i)
- {
- int pos = str[i] - 'a';
- p = p->next[pos];
- if(p == NULL)
- return 0;
- }
- return p->num;
- }
-
- int main()
- {
- char s[15];
-
- initialize();
- while(gets(s) && strlen(s))
- insert(s);
- while(gets(s))
- {
- int ans = find(s);
- printf("%d\n",ans);
- }
- return 0;
- }
- </span>