字典树浅析

前言 本文为原创,可能会存在一些知识点或理解上的问题,欢迎切磋和交流  ^_^

1. 字典树原理

有关字典树原理分析和介绍,网上铺天盖地的一大堆,这里不再赘述,仅说说自己对字典树应用场景和原理的理解。比如电子词典,可以用来查单词,几千个单词,长度不同,如何设计一个数据结构,来存放并且查找这些单词,来使得时间复杂度和空间复杂度为最低呢?以前在学校时,写过一个小项目,实现的功能就是电子词典,输入一个字符串,索引出该字符串的中文释义,当时项目里用到的算法有两种,一种是字符数组,一种是链表,用字符数组算法来存放单词,数组的每个元素都是一个对应,即元素存的是内容;同理,链表节点的每个data数据存放的也是内容,如果索引一个单词,就要把整个数组或链表遍历一遍,这种算法不管是空间复杂度,还是时间复杂度,效率肯定是极低的,当时做实验,如果是a开头的单词,查找比较快,如果是靠后开头的单词,查询就有明显的延时。

针对这种低级算法的劣势,字典树可以解决掉这些问题。为什么?今天看了一下,自己总结有两点:

(1) 字典树节点元素存放的不是内容,而是下一个节点的地址,这就节省了很多不必要的空间;

(2) 索引字符串,字典树并非所有节点内容都遍历一遍,而是通过获取字符串中每个字符在节点指针数组的偏移对应指针数组元素,不断查找前缀的方法,这样可以保证一遍下来能够找到或找不到字符串;

字典树的节点定义如下:

typedef struct Trie_Tree_Node
{
	struct Trie_Tree_Node *next[MAX];
	int count;
}Trie_Tree_Node,*Trie_Tree_Root;

节点结构体中包含两个元素,第一个是一个struct Trie_Tree_Node类型的指针数组,元素个数是26,因为字符串中字符总个数有26个,所以定义为26,count也是一个计数变量,它表示的是公共子串前缀的个数。

首先,字典树的根节点不会存放字符串的第一个字符元素,它就是一个头节点,表示是一颗空树;此时如果要存放一个字符串“love”到字典树,它是先把字符串按照一个一个的字符,先从l开始,计算l到字符a的偏移,然后取到根节点对应该偏移的指针数组的元素root->next[str[0]-’a’],判断该元素是否为null,如果为空,说明字典树里没有存放l字符,那么就重新申请一块类型为struct Trie_Tree_Node的内存空间,由new_node指向这块新内存的地址,然后建立根节点和新节点的关系,root->next[str[0] - ‘a’] = new_node,此时就存下了第一个字符l,并且把root指针指向这个新节点地址,root = root->next[str[0] - ‘a’];然后继续遍历字符串love,找到第二个字符o,继续计算字符o到字符a的偏移,在新root节点指针数组对应偏移的元素root->next[str[1]-’a’],判断该元素是否为null,如果为null,则说明字典树不存在该字符,如果存在,则说明已经有了,判断一下,发现不存在,继续新建一个new_node,逻辑如上;直到将所有的字符都存放到该树上。

这棵树长什么样呢?如下图所示。

 

2. 代码分析

字典树原理很简单,直接上代码,以下代码第一次代码优化后,再执行发现存在一点问题,后来修改验证,调试通过。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#define MAX 26

typedef struct Trie_Tree_Node
{
	struct Trie_Tree_Node *next[MAX];
	int count;
}Trie_Tree_Node,*Trie_Tree_Root;

Trie_Tree_Root trie_tree_root_init();
Trie_Tree_Node *trie_tree_node_init(Trie_Tree_Root *root,int id);
int trie_tree_count(Trie_Tree_Root root, const char *str);
int trie_tree_insert(Trie_Tree_Root root, const char *str);
int trie_tree_delete(Trie_Tree_Root root);
bool trie_tree_query(Trie_Tree_Root root, const char *str);

int main()
{
	bool flag;
	char str[100] = {0};
	Trie_Tree_Root root = trie_tree_root_init();
	
	trie_tree_insert(root, "lov");
	trie_tree_insert(root, "love");
	trie_tree_insert(root, "loving");
	trie_tree_insert(root, "lovable");
	trie_tree_insert(root, "like");

	printf("l count[%d]\n", trie_tree_count(root, "l"));	
	printf("lov count[%d]\n", trie_tree_count(root, "lov"));
	printf("like count[%d]\n", trie_tree_count(root, "like"));
	printf("aaa count[%d]\n", trie_tree_count(root, "aaa"));
	
	trie_tree_query(root, "lov") == true? printf("lov is exist\n"):printf("lov is not exist\n");
	trie_tree_query(root, "test") == true? printf("test is exist\n"):printf("test is not exist\n");
	
	trie_tree_delete(root);
	
	return 0;
}

Trie_Tree_Root trie_tree_root_init()
{
	int cnt;
	Trie_Tree_Root root = (Trie_Tree_Root)malloc(sizeof(Trie_Tree_Node));
	if(!root)
	{
		perror("create root failed!");
		return NULL;
	}
	for(cnt=0; cnt<MAX; cnt++)
	{
		root->next[cnt] = NULL;
	}
	root->count = 0;
	return root;
}

Trie_Tree_Node *trie_tree_node_init(Trie_Tree_Root *root,int id)
{
	int str_cnj;
	Trie_Tree_Node *new_node = (Trie_Tree_Node *)malloc(sizeof(Trie_Tree_Node));
	if(!new_node)
	{
		perror("create root failed!");
		return NULL;
	}
	new_node->count = 1;
	for(str_cnj=0; str_cnj<MAX; str_cnj++)
	{
		new_node->next[str_cnj] = NULL;
	}
	(*root)->next[id] = new_node;
	(*root) = (*root)->next[id];
	return (*root);
}

int trie_tree_insert(Trie_Tree_Root root, const char *str)
{
	int ret = 0;
	int str_cni,str_cnj,str_len;
	if(!root || !str)
	{
		perror("invalid paramter!");
		ret = -1;
		goto error_out;
	}
	str_len = strlen(str);
	for(str_cni=0; str_cni<str_len; str_cni++)
	{
		if(!root->next[str[str_cni] - 'a'])
		{
			if(!trie_tree_node_init(&root,str[str_cni] - 'a'))
			{
				trie_tree_delete(root);
				return -1;
			}
		}
		else
		{			
			root = root->next[str[str_cni] - 'a'];
			root->count++;		
		}
	}

error_out:
	return ret;
}

int trie_tree_count(Trie_Tree_Root root, const char *str)
{
	int str_cni, str_len;
	if(!root || !str)
	{
		perror("invalid paramter!");
		return -1;
	}
	str_len = strlen(str);
	for(str_cni=0; str_cni<str_len; str_cni++)
	{
		if(root->next[str[str_cni] - 'a'])
		{
			root = root->next[str[str_cni] - 'a'];
		}
		else
		{
			break;
		}
	}
	return str_cni<str_len? 0:root->count;
}

int trie_tree_delete(Trie_Tree_Root root)
{
	int cnt;
	if(!root)
	{
		perror("invalid paramter!");
		return -1;
	}
	for(cnt=0; cnt<MAX; cnt++)
	{
		if(root->next[cnt])
		{
			trie_tree_delete(root->next[cnt]);
		}
	}
	free(root);
	root = NULL;
}

bool trie_tree_query(Trie_Tree_Root root, const char *str)
{
	int ret = 0;
	int str_cni,str_len;
	Trie_Tree_Root troot = root;
	if(!troot || !str)
	{
		perror("invalid paramter!");
		return false;
	}
	str_len = strlen(str);
	for(str_cni=0; str_cni<str_len; str_cni++)
	{
		if(!troot->next[str[str_cni] - 'a'])
		{
			return false;
		}
		troot = troot->next[str[str_cni] - 'a'];
	}
	return true;
}

运行结果如下图所示:

3. 参考文章

https://blog.csdn.net/ns_code/article/details/21183495

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值