数据结构实现 8.1:字典树(C++版)

本文介绍了字典树(Trie树)的概念和基本框架,详细阐述了如何使用C++实现字典树的增加、查找等基本操作,并对算法复杂度进行了分析,指出增加和查找操作的时间复杂度为O(1)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概念及基本框架

字典树 顾名思义,就是用来存放单词的树,也称 Trie树 ,其本质上是一种多叉树,我们可以通过图示说明。

1

这里有一个灰色的根结点,不存储信息,然后下面的每个结点存储一个字母,利用结点颜色区分从根结点到当前结点构成的字符串是否是一个单词。这样,就可以构成一部字典。很显然,从字典中查找一个单词,与字典 的容量并无关系,而只与单词长度有关。这样只要建立好了字典,那么可以大大减小查找操作的时间复杂度。
当然,我们也可以把字母存储在路径上,只用结点颜色表示当前结点构成的字符串是否是一个单词。

2

我们先来定义一个字典树的结点,代码如下:

class TrieNode{
public:
	TrieNode(bool isWord = false){
		this->isWord = isWord;
		BSTMap<char, TrieNode*> *p = new BSTMap<char, TrieNode*>; 
		next = p;
	}
	TrieNode operator= (TrieNode& node){
		this->isWord = node.isWord;
		this->next = node.next;
		return *this;
	}
public:
	bool isWord;
	BSTMap<char, TrieNode*> *next;
};

这里使用了我们之前实现的 map 作为结点,其中 key 作为字母,value 用来存储子树。(很显然,每个结点都可以看成一棵多叉树的根)isWord 表示是否是一个单词,初始化默认为 false 。这里对 = 运算符进行了重载,方便我们后面的操作。
有了结点,字典树的大体框架如下:

class Trie{
public:
	Trie(){
		m_size = 0;
	}
	...
private:
	TrieNode root;
	int m_size;
};

root 表示字典树的根,不存储信息。
m_size 表示字典树的大小。
同样,为了保护数据,这些变量都放在 private 区。
接下来我们就对字典树的增加、查询以及一些其他基本操作用代码去实现。

2. 基本操作程序实现

2.1 增加操作

class Trie{
public:
	...
	void add(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				TrieNode* node = new TrieNode;
				cur->next->add(c, node);
			}
			cur = cur->next->get(c);
		}
		if (!cur->isWord){
			cur->isWord = true;
			m_size++;
		}
	}
	...
};

这里添加操作有两个:
第一个是添加字母结点,只有结点不存在才会执行此操作;
第二个是添加新单词,而只有字典中没有当前单词才会记录增加了新单词。

2.2 查找操作

这里我们提供两个查询函数,containsisPrefix ,函数实现代码如下。

class Trie{
public:
	...
	bool contains(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return cur->isWord;
	}
	bool isPrefix(string prefix){
		TrieNode *cur = &root;
		for (int i = 0; i < prefix.size(); ++i){
			char c = prefix[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return true;
	}
	...
};

contains 用于查找字典中是否包含该单词。
isPrefix 用于查找字典中是否包含该前缀串。

2.3 其他操作

字典树还有一些其他的操作,包括 字典树大小 等的查询操作。

class Trie{
public:
	...
	int size()const{
		return m_size;
	}
	bool isEmpty()const{
		return m_size == 0;
	}
	...
};

3. 算法复杂度分析

因为字典树的增加、查找操作只与单词长度有关,所以时间复杂度均是 O(1) 级别的。

3.1 增加操作

函数最坏复杂度平均复杂度
addO(1)O(1)

3.2 查找操作

函数最坏复杂度平均复杂度
containsO(1)O(1)
isPrefixO(1)O(1)

总体情况:

操作时间复杂度
O(1)
O(1)

4. 完整代码

程序完整代码(这里使用了头文件的形式来实现类)如下:
这里不再给出 BSTMap 实现代码,如有需要可查看 5.1

字典树 类代码:

#ifndef __TRIE_H__
#define __TRIE_H__

#include <string>
#include "BSTMap.h"

class TrieNode{
public:
	TrieNode(bool isWord = false){
		this->isWord = isWord;
		BSTMap<char, TrieNode*> *p = new BSTMap<char, TrieNode*>; 
		next = p;
	}
	TrieNode operator= (TrieNode& node){
		this->isWord = node.isWord;
		this->next = node.next;
		return *this;
	}
public:
	bool isWord;
	BSTMap<char, TrieNode*> *next;
};

class Trie{
public:
	Trie(){
		m_size = 0;
	}
	int size()const{
		return m_size;
	}
	bool isEmpty()const{
		return m_size == 0;
	}
	void add(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				TrieNode* node = new TrieNode;
				cur->next->add(c, node);
			}
			cur = cur->next->get(c);
		}
		if (!cur->isWord){
			cur->isWord = true;
			m_size++;
		}
	}
	bool contains(std::string word){
		TrieNode* cur = &root;
		for (int i = 0; i < word.size(); ++i){
			char c = word[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return cur->isWord;
	}
	bool isPrefix(string prefix){
		TrieNode *cur = &root;
		for (int i = 0; i < prefix.size(); ++i){
			char c = prefix[i];
			if (!cur->next->contains(c)){
				return false;
			}
			cur = cur->next->get(c);
		}
		return true;
	}
private:
	TrieNode root;
	int m_size;
};
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九霄星河

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值