首先,我们来简单了解一下字典树。
字典树是一种用于存储和检索字符串的一种树形数据结构,通过每个节点对应一个字符的方式相互连接,来存储字符串。其结构图大致如下图:
与传统的容器存储字符串相比,字典树有一个巨大的优点:可以通过字符前缀直接检索字符的存储情况,并判断字符前缀是否吻合进行剪枝处理。通俗来讲,就是检索效率高。
举个简单的例子,对于如上顺序表存储的字符串来说,假设共存储了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来实现)