Trie树—高级树型结构

文章目录

  • Trie树基本概念
  • Trie树特点
  • Trie树数据结构
  • Trie树基本操作
    • 插入
    • 查找
    • 删除
  • Trie树应用

Trie树基本概念

Trie 树中文名叫字典树、前缀树等等。这些名字暗示其与字符的处理有关,事实也确实如此,它主要用途就是将字符串(当然也可以不限于字符串)整合成树形。

它的核心思想就是通过最大限度地减少无谓的字符串比较,使得查询高效率,即「用空间换时间」,再利用共同前缀来提高查询效率。

典型应用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

Trie树特点

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符都不相同。

假设有5个字符串,它们分别是:Code,Cod,Cook,Five,File,Fat。按照Trie树的组织结构如下图:
Trie树—高级树型结构_第1张图片

Trie树数据结构

class Trie_node
{
public:
	Trie_node(char word = 0)
	{
		this->word = word;
		exist = false;
		count = 1; // 每个字符初始化就算一个前缀了
		memset(next, 0, sizeof(next)); // 子节点初始化
	}
private:
	char word;                    //该节点处的字符
	int count;                    // 统计单词前缀出现的次数
	Trie_node* next[26];          // 指向各个子树的指针
	bool exist;                   // 标记该结点处是否构成单词  
}

Trie树基本操作

插入

Trie树的插入操作很简单,其实就是将单词的每个字母逐一插入Trie树。插入前先看字母对应的节点是否存在,存在则共享该节点,不存在则创建对应的节点。

比如要插入新单词Cook,就有下面几步:

  • 插入第一个字母c,发现Root节点下方存在子节点c,则共享节点c。
  • 插入第二个字母o,发现c节点下方存在子节点o,则共享节点o。
  • 插入第三个字母o,发现o节点下方不存在子节点o,则创建子节点o。
  • 插入第三个字母k,发现o节点下方不存在子节点k,则子节点k。
    至此,单词cook中所有字母已被插入Trie树中,然后设置节点k中的标志位,标记路径root->c->o->o->k这条路径上所有节点的字符可以组成一个单词Cook。
void Trie_insert(Trie_node  *root, char* word)
{
	Trie_node *node = root;
	char *p = word;
	int id;
	while (*p)
	{
		id = *p - 'a';
		if (node->next[id] == NULL)
		{
			node->next[id] = new Trie_node(*p);
		}
		node = node->next[id];  // 每插入一步,相当于有一个新串经过,指针向下移动
		++p;
		node->count += 1;      // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)
	}
	node->exist = true;        // 单词结束的地方标记此处可以构成一个单词
}

查找

在Trie树中查找一个字符串的时候,比如查找字符串code,可以将要查找的字符串分割成单个的字符c,o,d,e,然后从Trie树的根节点开始匹配,当匹配到字符串最后一个字母时,如果它还存在于Trie树时且该节点对应的标志为被设置,则该单词存在。

bool Trie_search(Trie_node *root, char* word)
{
	Trie_node* node = root;
	char *p = word;
	int id;
	while (*p)
	{
		id = *p - 'a';
		node = node->next[id];
		++p;
		if (node == NULL)
			return false;
	}
	return node->exist;
}

删除

Trie树的删除操作需要考虑删除的节点所处的位置,这里分三种情况进行分析:

  • 删除整个单词
    从根节点开始查找第一个字符h。
    找到h子节点后,继续查找h的下一个子节点i。
    i是单词hi的标志位,将该标志位去掉。
    i节点是hi的叶子节点,将其删除。
    删除后发现h节点为叶子节点,并且不是单词标志位,也将其删除。
    这样就完成了hi单词的删除操作。

Trie树—高级树型结构_第2张图片

  • 删除前缀单词(比如cod)
    这种方式删除比较简单。只需要将cod单词整个字符串查找完后,d节点因为不是叶子节点,只需将其单词标志去掉即可。

  • 删除分支单词(比如cook)
    与删除整个单词情况类似,区别点在于删除到cook的第一个o时,该节点为非叶子节点,停止删除,这样就完成cook。

Trie树应用

  1. 前缀匹配

例如:找出一个字符串集合中所有以五分钟开头的字符串。我们只需要用所有字符串构造一个Trie树,然后输出以 五−>分−>钟 开头的路径上的关键字即可。

Trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。

  1. 字符串检索

给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,按最早出现的顺序写出所有不在熟词表中的生词。

检索/查询功能是Trie树最原始的功能。给定一组字符串,查找某个字符串是否出现过,思路就是从根节点开始一个一个字符进行比较:
如果沿路比较,发现不同的字符,则表示该字符串在集合中不存在。
如果所有的字符全部比较完并且全部相同,还需判断最后一个节点的标志位(标记该节点是否代表一个关键字)。

你可能感兴趣的:(数据结构)