经典的二叉查找树是基于某种键值对,当具体的键为字符串时,就有了极高性能的算法,单词查找树。
这种结构允许用户只比较字符串的所有字母,即可查到是否含有值,值是多少。从时间性能讲,应该是没有比这再快的了。比如红黑树,查找的时间性能是 O( logN ) , 其中N是比较字符串的次数,而单词查找树只与字符串长度有关,你可以认为,只比较一次字符串,相当于通用查找树的 O( 1 )。
这是很恐怖的性能,但是,代价是恐怖的空间占用。单词查找树的数据结构是一对多的链式结构,其中一个字母对应一个字母表,如果仅限英文环境,其大小是 256 如果单词查找树中的字符过长,那么储存一个字符串的大小就是 256 x 字符串长度 x 每个结构的大小。这将是极大的浪费。
好在如果仅就单词而言,不会浪费如此多的空间,但如果是某些特殊情景,比如基因序列,动辄数百万的碱基对长度,就必须将其特化为极小字母表,也就是每个字母只对应 大小为 4 的字母表,大大缩减空间,提升空间效率。
代码如下:
#include
#include
#include
namespace strST
{
// 下面我们用she sells sea shells by the shore
// 这几个键作为示例描述以下3个新方法。
// longestPrefixOf()接受一个字符串参数并返回符号表中该字符串的前缀中最长的键。
// 对于以上所有键,longestPrefixOf("shell")的结果是she,
// longestPrefixOf("shellsort")的结果是shells。
// keysWithPrefix()接受一个字符串参数并返回符号表中所有以该字符串作为前缀的键。
// 对于以上所有键,keysWithPrefix("she")的结果是she和shells
// keysWithPrefix("se")的结果是sells和sea。
// keysThatMatch()接受一个字符串参数并返回符号表中所有和该字符串匹配的键,
// 其中参数字符串中的点(“.”)可以匹配任何字符。
// 对于以上所有键,keysThatMatch(".he")的结果是she和the,
// keysThatMatch("s..")的结果是she和sea。
template <typename V> struct TrieST;
template <typename V> struct Node
{
Node() = default;
~Node()
{
for (int i = 0; i != R; ++i)
{
delete next[i];
}
delete[] next;
delete val;
}
private:
static const int R = 256;
V *val = nullptr;
Node **next = nullptr;
friend struct TrieST<V>;
};
template <typename V> struct TrieST
{
TrieST() = default;
~TrieST()
{
delete root;
root = nullptr;
cnt = 0;
}
// 向表中插入键值对(如果值为null则删除键key)
void put(const std::string &key, V val)
{
root = put(root, key, val, 0);
}
// 键key所对应的值(如果键不存在则返回null)
auto get(const std::string &key) const -> std::pair<bool, V>
{
Node<V> *nodePtr = get(root, key, 0);
if (nodePtr != nullptr && nodePtr->val != nullptr)
{
return {true, *nodePtr->val};
}
return {false, V()};
}
// 删除键key和它的值
void del(const std::string &key)
{
root = del(root, key, 0);
}
//只删除键对应的值
void deljustval(const std::string &key)
{
Node<V> *nodePtr = get(root, key, 0);
if (nodePtr != nullptr && nodePtr->val != nullptr)
{
delete nodePtr->val;
nodePtr->val = nullptr;
--cnt;
}
}
// 表中是否包含key的值
auto contains(const std::string &key) const -> bool
{
Node<V> *nodePtr = get(root, key, 0);
return nodePtr != nullptr && nodePtr->val != nullptr;
}
// 符号表是否为空
auto isEmpty() const -> bool
{
return cnt == 0;
}
// str 的前缀中最长的键
auto longestPrefixOf(const std::string &str) const -> std::string
{
int length = search(root, str, 0, 0);
return str.substr(0, length);
}
// 所有以 pre 为前缀的键
auto keysWithPrefix(const std::string &pre) const
-> std::vector<std::string>
{
std::vector<std::string> strVec;
collect(get(root, pre, 0), pre, strVec);
return strVec;
}
// 所有和 pat 匹配的键(其中 "." 能够匹配任意字符
auto keysThatMatch(const std::string &pat) const -> std::vector<std::string>
{
std::vector<std::string> strVec;
collect(root, "", pat, strVec);
return strVec;
}
// 键值对的数量
auto size() const -> int
{
return cnt;
}
// 符号表中的所有键
auto keys() const -> std::vector<std::string>
{
return keysWithPrefix("");
}
private:
// 递归获取 key 对应的 Node 指针
auto get(Node<V> *nodePtr, const std::string &key, int chOrder) const
-> Node<V> *
{
if (nodePtr == nullptr)
{
return nullptr;
}
if (chOrder == key.size())
{
return nodePtr;
}
unsigned char chr = key.at(chOrder);
return get(nodePtr->next[chr], key, chOrder + 1);
}
// 递归将 val 放入 Node 指针中
auto put(Node<V> *nodePtr, const std::string &key, const V &val,
int chOrder) -> Node<V> *
{
if (nodePtr == nullptr)
{
nodePtr = new Node<V>();
nodePtr->next = new Node<V> *[R]();
}
if (chOrder == key.size())
{
if (nodePtr->val == nullptr)
{
++cnt;
nodePtr->val = new V(val);
}
return nodePtr;
}
unsigned char chr = key.at(chOrder);
nodePtr->next[chr] = put(nodePtr->next[chr], key, val, chOrder + 1);
return nodePtr;
}
// 收集 pre 为前缀的键,加入 strVec
void collect(Node<V> *nodePtr, const std::string &pre,
std::vector<std::string> &strVec) const
{
if (nodePtr == nullptr)
{
return;
}
if (nodePtr->val != nullptr)
{
strVec.push_back(pre);
}
for (int i = 0; i != R; ++i)
{
if (nodePtr->next[i])
{
collect(nodePtr->next[i], pre + static_cast<char>(i), strVec);
}
}
}
// 收集符合 pat 模式的键,加入 strVec
void collect(Node<V> *nodePtr, const std::string &pre,
const std::string &pat, std::vector<std::string> &strVec) const
{
unsigned chOrder = pre.size();
if (nodePtr == nullptr)
{
return;
}
if (chOrder == pat.size() && nodePtr->val != nullptr)
{
strVec.push_back(pre);
}
if (chOrder == pat.size())
{
return;
}
char ch = pat.at(chOrder);
for (int i = 0; i != R; ++i)
{
if ((ch == '.' || ch == i) && (nodePtr->next[i]))
{
collect(nodePtr->next[i], pre + static_cast<char>(i), pat,
strVec);
}
}
}
//查找作为 str 前缀最长键的长度
auto search(Node<V> *nodePtr, const std::string &str, int chOrder,
int length) const -> int
{
if (nodePtr == nullptr)
{
return length;
}
if (nodePtr->val != nullptr)
{
length = chOrder;
}
if (chOrder == str.size())
{
return length;
}
unsigned char ch = str.at(chOrder);
return search(nodePtr->next[ch], str, chOrder + 1, length);
}
//递归删除键和值
auto del(Node<V> *nodePtr, const std::string &key, int chOrder) -> Node<V> *
{
if (nodePtr == nullptr)
{
return nullptr;
}
if (chOrder == key.size() && nodePtr->val)
{
delete nodePtr->val;
nodePtr->val = nullptr;
--cnt;
}
else
{
unsigned char c = key.at(chOrder);
nodePtr->next[c] = del(nodePtr->next[c], key, chOrder + 1);
}
if (nodePtr->val != nullptr)
{
return nodePtr;
}
for (int i = 0; i != R; ++i)
{
if (nodePtr->next[i] != nullptr)
{
return nodePtr;
}
}
delete nodePtr;
return nullptr;
}
// 根节点
Node<V> *root = nullptr;
// 字母表大小
static const int R = 256;
// 含有键值对数量
unsigned cnt = 0;
};
} // namespace strST
auto main(int /*argc*/, char * /*argv*/[]) -> int
{
strST::TrieST<int> a;
a.put("cs", 10);
a.put("csk", 10);
a.del("csk");
// auto b = a.get("c");
// b = a.get("csk");
// auto c = a.keys();
// c = a.keysWithPrefix("c");
// auto d = a.keysThatMatch("c..");
// auto e = a.longestPrefixOf("cspt");
return 0;
}