📜  在C++中使用智能指针和OOP进行Trie数据结构(1)

📅  最后修改于: 2023-12-03 15:37:34.786000             🧑  作者: Mango

在C++中使用智能指针和OOP进行Trie数据结构

在计算机科学中,Trie,又称字典树,是一种树形数据结构,用于高效地存储和检索关联数组。本文介绍如何在C++中使用智能指针和面向对象编程(OOP)来实现Trie数据结构。

Trie数据结构简介

Trie数据结构(字典树)是一种树形结构,用于存储关联数组。Trie的每个节点表示一个字符串的字符,每个节点都有0或多个指向子节点的链接。通常,Trie的根节点表示空字符串。除根节点外,每个节点都表示一个非空字符串,从根节点到该节点的路径表示该字符串。每个节点还可以存储与其相关联的值(如出现次数),或者将值存储在它所代表的字符串中。

下面是一个Trie的示例:

       root
      / | \
     c  t  f
    /   |   \
   a    o    a
  /      |     \
 t       p     t

在上面的示例中,Trie存储了字符串 "cat"、"cot"、"coa"、"fat"、"fot" 和 "foa"。

C++中的智能指针

C++中的智能指针是一种RAII(资源获取即初始化)机制,用于自动管理动态分配的内存。简单来说,智能指针允许程序员使用类似指针的语法来操作内存,而不用手动调用new和delete操作符。

C++中有三种常见的智能指针类型:unique_ptr、shared_ptr和weak_ptr。在本文中,我们将使用unique_ptr来管理Trie节点的内存。

unique_ptr

unique_ptr是一种独占的智能指针,即同一时刻只能有一个unique_ptr指向一个对象。当unique_ptr被销毁时,它所指向的对象也会被自动销毁。

下面是unique_ptr的一个示例:

#include <memory>

int main() {
    std::unique_ptr<int> ptr(new int(42));
    std::cout << *ptr << std::endl; // 输出 42
    return 0;
} // 此时,unique_ptr自动销毁,它所指向的int对象也会被销毁
在C++中实现Trie数据结构

我们可以通过一个TrieNode类来表示Trie中的每个节点。每个节点包含一个值(如出现次数)、0或多个子节点和一个单独的标志位,该标志位指示该节点是否存在一个与之关联的值。

下面是一个TrieNode类的简单实现:

class TrieNode {
public:
    TrieNode(char c) : m_data(c), m_isEnd(false) {}

    // 插入一个新的节点
    TrieNode* insert(char c) {
        m_children[c] = std::make_unique<TrieNode>(c);
        return m_children[c].get();
    }

    // 获取某个子节点
    TrieNode* get(char c) const {
        const auto iter = m_children.find(c);
        return iter == m_children.cend() ? nullptr : iter->second.get();
    }

    // 获取所有子节点
    std::vector<TrieNode*> getAll() const {
        std::vector<TrieNode*> res;
        for (auto& child : m_children) {
            res.push_back(child.second.get());
        }
        return res;
    }

    // 是否存在与之关联的值
    bool isEnd() const { return m_isEnd; }

    // 设置与之关联的值
    void setEnd() { m_isEnd = true; }

private:
    char m_data; // 节点代表的字符
    bool m_isEnd; // 是否存在与之关联的值
    std::unordered_map<char, std::unique_ptr<TrieNode>> m_children; // 子节点
};

在上面的代码中,我们使用std::unordered_map存储子节点,并使用unique_ptr来管理子节点的内存。当某个子节点不再需要时,它会自动被析构。

接下来,我们可以通过一个Trie类来包装TrieNode,以便在外部更方便地使用Trie数据结构。

下面是Trie类的简单实现:

class Trie {
public:
    Trie() : m_root(std::make_unique<TrieNode>(' ')) {}

    // 插入一个新的字符串
    void insert(const std::string& word) {
        auto node = m_root.get();
        for (const char c : word) {
            if (!node->get(c)) {
                node = node->insert(c);
            } else {
                node = node->get(c);
            }
        }
        node->setEnd();
    }

    // 查找某个字符串是否存在
    bool search(const std::string& word) const {
        const auto node = find(word);
        return node && node->isEnd();
    }

    // 查找某个前缀是否存在
    bool startsWith(const std::string& prefix) const {
        return find(prefix) != nullptr;
    }

private:
    // 查找某个字符串
    TrieNode* find(const std::string& word) const {
        auto node = m_root.get();
        for (const char c : word) {
            if (!node->get(c)) {
                return nullptr;
            } else {
                node = node->get(c);
            }
        }
        return node;
    }

private:
    std::unique_ptr<TrieNode> m_root; // Trie树的根节点
};

在上面的代码中,我们定义了insert、search和startsWith三个用于操作Trie数据结构的方法。对于每个方法,我们都可以使用unique_ptr来自动管理内存。

总结

本文介绍了如何在C++中使用智能指针和面向对象编程(OOP)来实现Trie数据结构。通过使用unique_ptr,我们可以简化内存管理并减少内存泄漏的风险,在编写大型程序时非常有用。