字典树

2018年01月16日 09:03 | 2950次浏览

字典树简介

    字典书(Trie Tree),又称单词查找树,是键树的一种,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

    字典树有3个基本性质:

    1、根节点不包含字符,其余的每个节点都包含一个字符;

    2、从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;

    3、每个节点的所有子节点包含的字符都不相同。


    字典树的结构一般如下图所示:

以字符串为例,我们可以将其存储结构写成如下形式:

#define MAX 26	//字符集的大小

/*
字典树的存储结构
*/
typedef struct Node
{
	struct Node *next[MAX];	//每个节点下面可能有MAX个字符
	int count;		//以从根节点到当前节点的字符串为公共前缀的字符串数目
}TrieNode,*TrieTree;

  这里,因为我们用的字符集为26个小写字母,因此MAX为26。可以将此数据存储结构与上面的存储图例相比较,以加深理解。这里很容易看出,字典树是典型的用空间换时间,存储空间极大,这在海量字符串时很适用。


    字典树有如下2个优点:   


    1.利用字符串的公共前缀来节约存储空间。

    2.最大限度地减少无谓的字符串比较,查询效率比较高。


字典树的应用

    字典树的应用很广泛,下面是摘自百度百科的3个应用介绍:

    1、在串快速检错中的应用

    字典树的应用很广泛,下面是摘自百度百科的3个应用简介:

    1、字典树在串的快速检索中的应用。

    给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。在这道题中,我们可以用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。

    2、字典树在“串”排序方面的应用

    给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。

    3、字典树在最长公共前缀问题的应用

    对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为最近公共祖先问题。


求字符串的公共前缀

    下面我们就以求字符串的公共前缀为例,来具体说明字典树的应用。

    在字典树中,主要的操作是查找,我们可以通过不断地插入来建造字典树,但是删除操作用到的很少。

    首先是存储结构,如前面的代码所示,其中:

   next是表示每层有多少种类的数,如果只是小写字母,则26即可,若改为大小写字母,则是52,若再加上数字,则是62了,这里根据题意来确定。
    count
表示以从根节点到该节点的字符串为公共前缀的字符串的数目。我们用它来来记录每个节点在词典中出现的次数,然后在对应的插入操作(Insert)中,每次插入时经过的节点作count++操作;这样进行搜索(Search)时,若搜索成功只需要返回搜索终止节点的count变量的值,就是以某个字符串为前缀的单词总数。

    如果明白了字典树的存储,下面的操作思想也就一目了然了,插入和查询都是从根节点依次向下,而字典树的销毁则通过递归的思想来实现。我这里直接贴出所有操作(插入、查找、销毁等)的代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"data_structure.h"

/*
创建一棵仅有根节点的字典树
*/
TrieTree create_TrieTree()
{
	TrieTree pTree = (TrieTree)malloc(sizeof(TrieNode));
	pTree->count = 0;
	int i;
	for(i=0;i<MAX;i++)
		pTree->next[i] = NULL;
	return pTree;
}

/*
插入字符串str到字典树pTree中
由于不可能改变根节点,因此这里不需要传入pTree的引用
*/
void insert_TrieTree(TrieTree pTree,char *str)
{
	int i;
	TrieTree pCur = pTree;	//当前访问的节点,初始指向根节点
	int len = strlen(str);
	for(i=0;i<len;i++)
	{
		int id = str[i] - 'a';	//定位到字符应该在的位置
		if(!pCur->next[id])
		{	//如果该字符应该在的位置为空,则将字符插入到该位置
			int j;
			TrieNode *pNew = (TrieNode *)malloc(sizeof(TrieNode));
			if(!pNew)
			{
				printf("pNew malloc fail\n");
				exit(-1);
			}
			pNew->count = 1;
			for(j=0;j<MAX;j++)
				pNew->next[j] = NULL;
			//指针后移,比较下一个字符
			pCur->next[id] = pNew;
			pCur = pCur->next[id]; 
		}
		else
		{	//如果该字符应该在的位置不空,则继续比较下一个字符
			pCur = pCur->next[id];
			pCur->count++;	//每插入一个字符,公共前缀数目就加1
		}
	}
}

/*
统计字典树pTree中以str为前缀的字符串的数目
*/
int count_TrieTree(TrieTree pTree,char *str)
{
	int i;
	TrieTree pCur = pTree;
	int len = strlen(str);
	for(i=0;i<len;i++)
	{
		int id = str[i] - 'a';
		if(!pCur->next[id])
			return 0;
		else
			pCur = pCur->next[id];
	}
	return pCur->count;
}

/*
销毁字典树
*/
void destroy_TrieTree(TrieTree pTree)
{
	int i;
	//递归释放每个节点的内存
	for(i=0;i<MAX;i++)
		if(pTree->next[i])
			destroy_TrieTree(pTree->next[i]);
	free(pTree);
}

测试运行结果如下:

完整源码下载

完整源码下载地址:http://download.csdn.net/detail/mmc_maodun/7036409

文章来源:



小说《我是全球混乱的源头》

感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程