使用 class C++ 的 Trie 数据结构

Trie data structure using class C++

我正在尝试使用 class 在 C++ 中实现 trie 数据结构。在 TrieNode class 我有一个 TrieNode *children[26]; 数组和一个 isEndOfWord 布尔值来确定它是否是结束词。在同一个 class 中,我还有其他适合像 getters 和 setters 这样的函数,另外还有插入和搜索。

每当我尝试添加一个新词时,它也会通过将 isEndOfWord 设置为 true 来将每个词末尾的 bool 值设置为 true。但是在搜索功能中,它并不能确定单词的结尾。请指导我,因为我是这个数据结构的新手,请评论我编写代码的方式以及编写代码的合适方式(如果有兴趣,以专业方式)。谢谢!

#include<cstdio>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

using namespace std;
class TrieNode{

    private:

        TrieNode *children[26];
        bool isEndOfWord;
    public:

        TrieNode(){
            for(int i = 0; i < 26; i++){

                children[i] = NULL;
            }
            isEndOfWord = false;
        }

        bool checkNull(char temp){
            cout<<"\nIncheckNULL "<<temp<<" "<<(temp - 'a')<<" \n";
            if(children[temp - 'a'] == NULL){

                return true;
            }
            else{

                return false;
            }
        }

        void setNode(char temp){
            cout<<"Setting node \n";
            children[temp - 'a'] = new TrieNode();
        }

        TrieNode *getNode(char temp){

            return children[temp - 'a'];
        }

        void setEndWord(){

            this->isEndOfWord = true;
        }

        bool getEndWord(){

            return this->isEndOfWord;
        }

        void insert(TrieNode*, string);
        bool search(TrieNode*, string);

};

void TrieNode::insert(TrieNode *root, string key){

    TrieNode *crawl = root;
    //cout<<"key is "<<key<<endl;
    int length = sizeof(key)/sizeof(key[0]);
    //cout<<"find length\n";
    for(int i = 0; key[i] != '[=11=]'; i++){
        cout<<"TEST null check key is "<<key[i]<<endl;
        if(crawl->checkNull(key[i])){
            cout<<"null check key is "<<key[i]<<endl;
            crawl->setNode(key[i]);
            crawl = crawl->getNode(key[i]);

            if(key[i + 1] == '[=11=]'){
                cout<<"In setting end word\n";
                if(crawl->getEndWord()){

                    cout<<"Word already exists";
                }
                else{

                    crawl->setEndWord();
                    cout<<"End word setted "<<crawl->getEndWord()<<endl;
                }
            }
        }
        else{

            if(key[i + 1] == '[=11=]'){
                cout<<"In setting end word\n";
                if(crawl->getEndWord()){

                    cout<<"Word already exists";
                }
                else{

                    crawl->setEndWord();
                    cout<<"End word setted\n";
                }
            }
            else{

                crawl = crawl->getNode(key[i]); 
            }
        }
    }
}

bool TrieNode::search(TrieNode *root, string key){

    TrieNode *crawl = root;
    cout<<"key is "<<key<<endl;
    cout<<"\n In search\n";
    int length = sizeof(key)/sizeof(key[0]);
    for(int i = 0; key[i] != '[=11=]'; i++){

        if(crawl->checkNull(key[i])){
            cout<<"INside search checknull"<<endl;
            cout<<"Word does not exists"<<"sorry"<<endl;
            break;
        }
        else{
            cout<<"IN each character getting getEndWord "<<crawl->getEndWord()<<endl;
            if(key[i + 1] == '[=11=]'){

                if(crawl->getEndWord()){

                    cout<<"Word Exists";
                }
                else{

                    cout<<"Word does not exists"<<"sorry"<<endl;
                    break;
                }
            }
            else{

                crawl = crawl->getNode(key[i]); 
            }
        }
    }

}

int main(){

    TrieNode *root = new TrieNode();
    cout<<"starting"<<endl;
    root->insert(root, "hello");
    cout<<"first added"<<endl;
    root->insert(root, "anna");
    root->insert(root, "anni");
    cout<<"words added"<<endl;
    root->search(root, "hello");
    root->search(root, "anny");

}

我会就很多事情向您提供反馈,但这不是代码审查网站,它是针对特定问题的。不过,我会简要指出一些我注意到的事情:

1) 不包括 C headers;改用c++。

2) 字符串是什么类型?

3) 你计算了长度(不正确,假设问题 2 的答案是 "the standard c++ string class"),但你没有使用它。

4) search() return 是一个布尔值,但你 return 什么都没有。当你找到一个单词的结尾时,你应该从函数中return。

5) search() 在 for 循环的顶部调用 checkNull() 而不确保它不为空。在此之后:crawl = crawl->getNode(key[i]); 它可能为 null,但随后您循环并遍历指针而不对其进行测试。

6) setNode 是一个 public 函数,无条件地覆盖给定变量的槽中的任何内容。如果有人用相同的字符两次调用它并泄漏(并且可能会丢失树中的数据),您可以破坏现有的 child。

7) 搜索不需要是 TrieNode 的成员。事实上,它不会通过 "this" 访问任何数据。您可能根本不希望 TrieNode 是 public,而是 Trie 的内部实现细节,这是搜索功能应该存在的地方,根应该存储和管理的地方。

8) 在 C++ 中使用 nullptr 而不是 NULL

9) 看起来你需要调试 search(),因为当你检查词尾时它不在最后一个字母上。

10) 你需要一个析构函数并且需要释放你的节点。或者将它们存储在 unique_ptr<> 中,以便在 object 超出范围时自动删除。

11) 不要在 headers 中 "using namespace std;"。将您的 headers 包含在我的代码中会使您变得有毒。

您的插入和搜索功能可以稍微简化一下。

考虑一下。 (阅读下面代码中的注释,它们说明了代码的作用)

void TrieNode::insert(TrieNode *root, string key){

    TrieNode *crawl = root;
    if (!crawl) {
        crawl = new TrieNode();
    } 
    cout << "Adding " << key << " to the trie" << endl;
    for (int index = 0, auto str_iterator = str.begin(); str_iterator < str.end(); ++str_iterator, ++index) {
        char key_char = *str_iterator;
        if(crawl -> checkNull(key_char)){
            // If a node representing the char does not exist then make it 
            crawl -> setNode(key_char);
        }
        crawl = crawl -> getNode(key_char);
        if (index == key.length() - 1) {
            // We are at the last character, time to mark an end of word
            crawl -> setEndWord();
        }
    }
}

bool TrieNode::search(TrieNode *root, string key){

    TrieNode *crawl = root;
    if (!crawl) {
        cout << "Trie is empty!" << endl;
        return false;
    } 
    cout << "Searching for " << key << " in the trie" << endl;
    for (int index = 0, auto str_iterator = str.begin(); str_iterator < str.end(); ++str_iterator, ++index) {
        char key_char = *str_iterator;
        if(crawl -> checkNull(key_char)){
            cout << "Key is not in the trie" << endl;
            return false;
        }
        crawl = crawl -> getNode(key_char);
        if (index == key.length() - 1) {
            if (!(crawl -> getEndWord())) {
                cout << "Word is physically present in trie, but not present as a distinct word" << endl;
                return false;
            } else {
                return true;
            }
        }
    }
    cout << "Code should not reach here" << endl; // IMO throw an exception I guess
    return false;
}

利用 C++ 的强大功能std::string

而且你的整个 temp - 'a' 逻辑对我来说有点不确定。除非我需要

,否则我不会过多地使用 ASCII 值

为什么要包含一大堆 C headers?只需 iostream 就足以完成 cstdio 所做的事情。

if(!ptr) 是一种更自然的检查 NULL 的方法。

在生产中不要使用 using namespace std;,而只是在 coutendl 之类的东西前加上 std::。这样做的原因是为了避免污染标准命名空间。

读一本很好的 CPP OOP 书 :)。对你有很大帮助。

我也喜欢 annaanni。你的 anna 和 anni 一定很自豪能进入你的 trie :D

insertsearch 函数一团糟。 他们使用相当人为的方法来检查字符串的结尾,不必要地重复并且在其中一个分支中存在错误。

这里有更简单的版本。 他们使用字符串size作为循环边界,循环结束时需要的动作在循环之后进行,这样更自然。

void TrieNode::insert(TrieNode *root, string key){
    TrieNode *crawl = root;
    for(int i = 0; i < (int) (key.size()); i++){
        if(crawl->checkNull(key[i])){
            crawl->setNode(key[i]);
        }
        crawl = crawl->getNode(key[i]);
    }
    crawl->setEndWord();
}

bool TrieNode::search(TrieNode *root, string key){
    TrieNode *crawl = root;
    for(int i = 0; i < (int) (key.size()); i++){
        if(crawl->checkNull(key[i])){
            return false;
        }
        crawl = crawl->getNode(key[i]);
    }
    return crawl->getEndWord();
}

我使用了相同的样式,但为了便于阅读省略了调试输出。

此外,代码实际上并没有使用 search 作为函数,它没有 return 一个值。 相反,它依靠调试输出来显示结果。 现在已更正。 main 补充这些的功能如下。

int main(){
    TrieNode *root = new TrieNode();
    cout<<"starting"<<endl;
    root->insert(root, "hello");
    cout<<"first added"<<endl;
    root->insert(root, "anna");
    root->insert(root, "anni");
    cout<<"words added"<<endl;
    cout << root->search(root, "hello") << endl;  // 1
    cout << root->search(root, "anny") << endl;  // 0
}