杭电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分配内存*/   
    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;//先由p指向字典树
	//把字符加入字典树
	for(int i = 0 ; i < len ; ++i)
	{
		int pos = str[i] - 'a';//转化为坐标
		if(p->next[pos] == NULL)//如果指向的结点为空,新建结点 
		{
			//new一个新结点
			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分配内存*/ 
	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;//先由p指向字典树的根,从而找到这棵字典树 
	//把字符加入字典树
	for(int i = 0 ; i < len ; ++i)
	{
		int pos = str[i] - 'a';//转化为坐标
		if(p->next[pos] == NULL)//如果指向的结点为空,新建结点 
		{
			//new一个新结点
			struct Trie *temp = (struct Trie*)malloc(sizeof(struct Trie));
			temp->num = 1;//这个地方是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;
}
</pre></div><div style="top: 139px;"><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的<span style="box-sizing: border-box;">公共前缀</span>来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。<br style="box-sizing: border-box;" /></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">说到底,其实不难算是算法应当归为数据结构,一种特殊的树。</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">字典树有三个特点,非常重要,也是构建字典树的三个标准原则吧。</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">1.根节点为空</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">2.树的每一层的元素独一无二不可重复</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">3.从根节点到某一节点的路径就是该节点对应的字符串。</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">比如说这个样例</span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">banana</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">band</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">bee</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">absolute</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';"><span style="box-sizing: border-box; white-space: pre-wrap;">acm</span><br style="box-sizing: border-box;" /></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">这五个单词组成的字典树应该如下图所示:</span></span></p><img src="https://img-blog.youkuaiyun.com/20141023103825967?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvTWxvbmc1NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; color: rgb(86, 95, 105); font-family: 'Microsoft Yahei'; font-size: 14px; line-height: 25.2px;" /><br style="box-sizing: border-box; color: rgb(86, 95, 105); font-family: 'Microsoft Yahei'; font-size: 14px; line-height: 25.2px;" /><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 2; font-family: 'Microsoft Yahei'; font-size: 14px; color: rgb(86, 95, 105);"></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; white-space: pre-wrap;"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';">(中间有些单词很长省略了..)这个图应该很直观了。</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';"><span style="box-sizing: border-box; white-space: pre-wrap;">字典树的操作(看资料一般定义了四个操作。创建 添加 删除 查找。由于删除操作不常用于是在这就不说了):</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';"><span style="box-sizing: border-box; white-space: pre-wrap;">1.创建</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';"><span style="box-sizing: border-box; white-space: pre-wrap;">创建应该分成两个部分。定义结点和建立树。</span></span></p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; line-height: 26px; font-family: Arial; font-size: 14px; color: rgb(51, 51, 51);"><span style="box-sizing: border-box; font-family: 'Microsoft YaHei';"><span style="box-sizing: border-box; white-space: pre-wrap;">定义结点的代码如下:</span></span></p><pre name="code" class="cpp" style="box-sizing: border-box; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 13px; white-space: pre-wrap; padding: 0px; margin-top: 0px; margin-bottom: 0px; line-height: 1.5em; color: rgb(51, 51, 51); word-break: break-all; word-wrap: break-word; border: 1px solid rgb(204, 204, 204); border-radius: 4px; background-color: rgb(245, 245, 245);">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分配内存*/   
    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;//先由p指向字典树
	//把字符加入字典树
	for(int i = 0 ; i < len ; ++i)
	{
		int pos = str[i] - 'a';//转化为坐标
		if(p->next[pos] == NULL)//如果指向的结点为空,新建结点 
		{
			//new一个新结点
			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分配内存*/ 
	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;//先由p指向字典树的根,从而找到这棵字典树 
	//把字符加入字典树
	for(int i = 0 ; i < len ; ++i)
	{
		int pos = str[i] - 'a';//转化为坐标
		if(p->next[pos] == NULL)//如果指向的结点为空,新建结点 
		{
			//new一个新结点
			struct Trie *temp = (struct Trie*)malloc(sizeof(struct Trie));
			temp->num = 1;//这个地方是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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值