【字典树】2828 -> 字典树
字典树
先上个定义。
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
字典树有两种构建方式,一种是指针构建,一种是数组构建。思路答题一样不过自我感觉指针好理解,数组好写好构建。可以先看指针是怎么做的再来看数组会有不一样的感觉。都这样说了就先上指针的做法。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int cnt;//判断是否为单词
struct tree *next[26];
}tree;
int top;
struct tree a[2000000];//记得申请静态
tree *createtree()//初始化树
{
int i;
tree *treeNode = &a[top++];
for(i = 0; i < 26; i++)
treeNode->next[i] = NULL; //节点的next全指向为NULL
treeNode -> cnt = 0;
return treeNode;
}
int Inserttree(tree *ptrRoot, char *str)
{
int i, index;
tree *tempNode = ptrRoot;//用以操作树的节点的指针
for( i = 0 ;i < strlen(str); i++)
{
index = str[i] - 'a';
if(tempNode->next[index]!=NULL)//也从树的头处开始询问当前字母是否存在
{
tempNode = tempNode->next[index]; //存在,进入该字母所在的节点
continue;
}
tempNode->next[index] = createtree();//不存在,先建立这样一个节点
tempNode = tempNode->next[index];
}
tempNode->cnt++;//这里循环完成到达单词结尾,标记为单词
return 0;
}
int SearchTrieTree(tree *ptrRoot, char *str)
{
int i, index;
tree *tempNode = ptrRoot;
for(i = 0 ;i < strlen(str); i++)//遍历前缀
{
index = str[i] - 'a';
if(tempNode->next[index]!=NULL)//也从树的头处开始询问当前字母是否存在
{
tempNode = tempNode->next[index];//存在进入下一指针
}
else
{
return 0;//不存在直接返回
}
}
if(tempNode->cnt)//!!!这就是为什么要标记单词结尾因为可能出现前缀比较相同但只事子串的可能性,比如:absd上找ab前缀相同但并没有ab这个单词。
{
return tempNode->cnt;
}
else
{
return 0;
}
}
int main()
{
int n,m;
char s[15];
while(~scanf("%d %d", &n, &m)&&n&&m)
{
top = 0;
tree *tree = createtree();
getchar();
while(n--)
{
gets(s);
Inserttree(tree, s);
}
while(m--)
{
gets(s);
int res = SearchTrieTree(tree, s);
if(res == 1)
printf("Yes\n");
else
printf("No\n");
}
}
}
接下来是数组构造,好写但第一次看真的不好理解。可以手写一下感受一波。说明下参数 p表示当前节点的地址,j表示字母的通道,数值就是通到链接下个头节点的地址了 关键就是这句话。
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#define N 2100000//一定要开大!!!
int str[N][26];//看成N个连续的地址空间 每个空间有26个通道和一个计数
int num;//模拟连续内存的首地址,是几无所谓啦,默认0
int mark[N];
void Insert(char *s)
{
int i, len = strlen(s);
int p = 0; //类似指针,数值就是地址了
for(i = 0; i < len; i++)
{
int t = s[i] - 'a';
if(!str[p][t]) str[p][t] = ++num;//0地址的空间作为root了,判断当前字母是否存在。不存在,选取下一个空的空间连通,作为该字母的节点
p = str[p][t];//进入当前字母链接的节点
}
mark[p]++;//老样子标记单词结尾
}
int find(char *s)//比较和链式的没区别不多BB了
{
int len = strlen(s);
int p = 0, i;
for(i = 0; i < len; i++)
{
int t = s[i] - 'a';
if(!str[p][t]) return 0;
p = str[p][t];
}
return mark[p];
}
int main()
{
int n, m, i, j;
char a[55];
while(~scanf("%d%d", &n, &m))
{
if(!n && !m)
return 0;
else
{
memset(str, 0, sizeof(str));//记得初始化!
memset(a, '\0', sizeof(a));//记得初始化!!
memset(make, 0, sizeof(make));//记得初始化!!!
num = 0;
while(n--)
{
getchar();
scanf("%s", a);
Insert(a);
}
while(m--)
{
getchar();
scanf("%s", a);
if(find(a))
printf("Yes\n");
else
printf("No\n");
}
}
}
return 0;
}
参考博客:字典树(链式+数组模拟)–最基础的算法,最详细的注释,写的真的很好。