2022-04-27 空间换时间的典范,R(字母表)单词查找树(C++)

经典的二叉查找树是基于某种键值对,当具体的键为字符串时,就有了极高性能的算法,单词查找树。

这种结构允许用户只比较字符串的所有字母,即可查到是否含有值,值是多少。从时间性能讲,应该是没有比这再快的了。比如红黑树,查找的时间性能是 O( logN ) , 其中N是比较字符串的次数,而单词查找树只与字符串长度有关,你可以认为,只比较一次字符串,相当于通用查找树的 O( 1 )。

这是很恐怖的性能,但是,代价是恐怖的空间占用。单词查找树的数据结构是一对多的链式结构,其中一个字母对应一个字母表,如果仅限英文环境,其大小是 256 如果单词查找树中的字符过长,那么储存一个字符串的大小就是 256 x 字符串长度 x 每个结构的大小。这将是极大的浪费。

好在如果仅就单词而言,不会浪费如此多的空间,但如果是某些特殊情景,比如基因序列,动辄数百万的碱基对长度,就必须将其特化为极小字母表,也就是每个字母只对应 大小为 4 的字母表,大大缩减空间,提升空间效率。

代码如下:

#include <iostream>
#include <string>
#include <vector>

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

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

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

打赏作者

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

抵扣说明:

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

余额充值