[C++]数据结构:浅析字典树及字典树的实现

首先,我们来简单了解一下字典树。

字典树是一种用于存储和检索字符串的一种树形数据结构,通过每个节点对应一个字符的方式相互连接,来存储字符串。其结构图大致如下图:

 

与传统的容器存储字符串相比,字典树有一个巨大的优点:可以通过字符前缀直接检索字符的存储情况,并判断字符前缀是否吻合进行剪枝处理。通俗来讲,就是检索效率高。

举个简单的例子,对于如上顺序表存储的字符串来说,假设共存储了m个字符串,每个字符串平均长度为n,若我们要判断"cake"是否存在于表中,则需要将表从头到尾遍历一遍,并对每个字符串从头到尾检索一遍是否与给定字符串吻合。这样,在经过了时间复杂度为o(m*n)的检索后,我们终于发现,"cake"确实在表中。

而字典树则不一样,为了寻找"cake",我们只需要从头结点通过对字符'c'的映射直接找到对应的节点,接着同样的方式找到'a'->'k'->'e',我们就能直接得出相同的结论,这个过程我们甚至没有进行任何遍历。因此,用于存储和检索字符串的字典树是有着极高查找效率的。

下面,我们来简单介绍一下字典树的结构吧。

对于一个字典树的节点,通常由三部分组成:子节点列表(这里我选择用unordered_map)、bool类型的结尾判断、当前节点对应表示的字符串。

子节点列表,很好理解,毕竟是树结构,我们需要建立父节点访问子节点的通道。

而这个bool类型的参数,则反映了当前节点是否为一个字符串的结尾。比如我要存储一个"cake"字符串,那么我在'e'对应的节点上将这个bool类型的值设置为true。这样,在我们对字典树进行检索时,我们就知道'e'节点和他之前的所有节点构成了一个完整的字符串。

另外,我们知道树形结构从父到子易,从子到父难,对每个节点存储对应的字符串,可以方便遍历过程中对任一位置获得对应的字符串形式。

当然,通常根节点处不会存储字符,而是用根节点的数量反映字典树的数量。

接下来,附上我自己实现的字典树结构:

Trie.h

#pragma once
#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;

class Node
{
private:
	string _str;
	bool _isEnd;
	unordered_map<char, Node> _son;
	friend class Trie;

public:
	Node(string str = "\b", bool flag = false) :_str(str), _isEnd(flag) {}//用"\b"来判定一个节点为空
};

class Trie
{
private:
	//用一个静态成员变量存储字符排列顺序
	static string characters;

	Node _root;
	size_t _size;

	static inline void PrintVector(vector<string> vstr)
	{
		for (string str : vstr)
		{
			cout << str << "   ";
		}
	}

	bool _insert(string str, Node& node, int pos = 0);
	bool _remove(string str, Node& node, bool& flag, int pos = 0);
	bool _search(string str, Node node, int pos = 0);
	void _GetTrieInOrder(Node node, vector<string>& ret);
	void _SearchByPrefix(string str, Node node, vector<string>& ret, int pos = 0);
	void _SearchBySubword(string str, Node node, vector<string>& ret, int pos = -1);

public:
	/// <summary>
	/// 得到字典树的大小
	/// </summary>
	inline int size()
	{
		return _size;
	}

	/// <summary>
	/// 向字典树插入一个字符串
	/// </summary>
	inline bool insert(string str)
	{
		if (_insert(str, _root))
		{
			_size++;
			return true;
		}
		return false;
	}

	/// <summary>
	/// 从字典树中移除一个字符串
	/// </summary>
	inline bool remove(string str)
	{
		bool flag = false;
		if (_remove(str, _root, flag))
		{
			_size--;
			return true;
		}
		return false;
	}

	/// <summary>
	/// 搜索字符串是否存在字典树中
	/// </summary>
	inline bool search(string str)
	{
		return _search(str,_root);
	}

	/// <summary>
	/// 按序获取所有字符串
	/// </summary>
	inline vector<string> GetTrieInOrder()
	{
		vector<string> ret;
		_GetTrieInOrder(_root, ret);
		return ret;
	}

	/// <summary>
	/// 按序打印所有字符串
	/// </summary>
	inline void GetAndShowTireInOrder()
	{
		PrintVector(GetTrieInOrder());
	}
	
	/// <summary>
	/// 在字典树中根据前缀搜索所有字符串
	/// </summary>
	inline vector<string> SearchByPrefix(string str)
	{
		vector<string> ret;
		_SearchByPrefix(str, _root, ret);
		return ret;
	}

	/// <summary>
	/// 在字典树中根据前缀搜索所有字符串并打印
	/// </summary>
	inline void SearchAndShowByPrefix(string str)
	{
		PrintVector(SearchByPrefix(str));
	}

	/// <summary>
	/// 在字典树中根据前缀搜索字符串并返回其个数
	/// </summary>
	inline int CountByPrefix(string str)
	{
		return SearchByPrefix(str).size();
	}

	/// <summary>
	/// 在字典树中根据子串搜索所有字符串
	/// </summary>
	inline vector<string> SearchBySubword(string str)
	{
		vector<string> ret;
		_SearchBySubword(str, _root, ret);
		return ret;
	}

	/// <summary>
	/// 在字典树中根据子串搜索所有字符串并打印
	/// </summary>
	inline void SearchAndShowBySubword(string str)
	{
		PrintVector(SearchBySubword(str));
	}

	/// <summary>
	/// 在字典树中根据子串搜索所有字符串并返回其个数
	/// </summary>
	inline int CountBySubword(string str)
	{
		return SearchBySubword(str).size();
	}

	Trie(Node root = Node()) :_root(root), _size(0) {}
	~Trie(){}
};

Trie.cpp

#include"Trie.h"

string Trie::characters = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ -,.?!;()+*/";

bool Trie::_insert(string str, Node& node, int pos)
{
	node._str = str.substr(0, pos);
	if (pos == str.size())
	{
		if (!node._isEnd)
		{
			node._isEnd = true;
			return true;
		}
		return false;
	}
	return _insert(str, node._son[str[pos]], pos + 1);
}

bool Trie::_remove(string str, Node& node, bool& flag, int pos)
{
	bool isExist = false;
	if (pos == str.size())
	{
		if (node._isEnd)
		{
			isExist = true;
			node._isEnd = false;
		}
		if (node._son.size() == 0)
		{
			flag = true;
		}
		return isExist;
	}
	isExist = _remove(str, node._son[str[pos]], flag, pos + 1);
	if (flag)
	{
		if (node._son.size() <= 1)
		{
			node._son.erase(node._son.begin(), node._son.end());
		}
		else
		{
			flag = false;
		}
	}
	return isExist;
}

bool Trie::_search(string str, Node node, int pos)
{
	if (pos == str.size())
	{
		if (node._isEnd)
			return true;
		else
			return false;
	}
	return _search(str, node._son[str[pos]], pos + 1);
}

void Trie::_GetTrieInOrder(Node node, vector<string>& ret)
{
	if (node._isEnd)
		ret.push_back(node._str);
	for (char c: characters)
	{
		if (node._son[c]._str != "\b")
			_GetTrieInOrder(node._son[c], ret);
	}
}

void Trie::_SearchByPrefix(string str, Node node, vector<string>& ret, int pos)
{
	if (pos == str.size())
	{
		if (node._isEnd)
		{
			_GetTrieInOrder(node, ret);
			return;
		}
		for (char c : characters)
			if (node._son[c]._str != "\b")
				_SearchByPrefix(str, node._son[c], ret, pos);
		return;
	}
	_SearchByPrefix(str, node._son[str[pos]], ret, pos + 1);
}

void Trie::_SearchBySubword(string str, Node node, vector<string>& ret, int pos)
{
	if (pos == -1)
	{
		for (char c: characters)
		{
			if (str[0] == node._son[c]._str.back())
				pos++;
			if (node._son.empty() || node._son[c]._str == "\b") continue;
			_SearchBySubword(str, node._son[c], ret, pos);
			if (str[0] == node._son[c]._str.back())
				pos--;
		}
		return;
	}
	_SearchByPrefix(str, node, ret, 1);
	return;
}

(注:此处按前缀搜索及按子串搜索都是按序返回,顺序优先级为可通过修改characters来实现)

由于是自己手写的代码,可能有很多不足,欢迎各路大犇指点!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值