Trie树

tags:算法

今天在hiro上做了一个题目,要求输入一个字符串,在一个词典中找到以这个字符串开头的字符串有多少个。比如,词典里有abc,ab,adf三个单词,那么以a开头的单词有3个,以ab开头的单词有2个,以ad开头的单词有1个,以abcdefg开头的单词有0个。
就是这样一个规则。

题目中还提示了一个Trie树的概念,然后我去学习了Trie树是长啥样的,搞明白了之后就用C++来写这个题目,C++真的很难用啊。各种奇怪的错误。

Trie树如何存储字符串?

这里写图片描述
如上图所示,trie树就是一个多叉树,root节点是不存储字符的,从root节点往下,每一层都有各种字符。比如good这个单词,在第一层中放g,第二层中放o,第三层中放o,第四层中放d,如果还有个goose这样的单词,那么上面都是相同的,第四层应该放s,第五层放e。
Trie树中就是这样放数据的,所以,对于英文单词(26个字母)来说,每一个节点下面应该都跟着26个子节点,说实话,这个空间确实占用很大。

Trie树是如何搜索字符串并判断有多少字符串是以关键字开头的?

再画一个图。
这里写图片描述
假设有
abc
abd
a
adf
四个单词,那么以a开头的有4个,以ab开头的有2个,有ad开头的有一个。
我想的办法是,在读取字符串并插入到trie树中的时候,因为读取字符串后,是挨个挨个的读取每一个字符的,所以当读取abc的时候,先读取a,然后就在第一层的a节点将a赋值给它,并且,让它一个计数器加1,那么也就是说,如果任意一个以a开头的单词,在进行插入树的操作时,都会对第一层的a节点进行一次+1的操作,也就是说,a节点上的数值,表示了以a开头的单词有多少个。
同样的,子节点中,数值的大小,也表示了经过这个节点的次数,比如第二层的b,数值为2,就可以表示以ab开头的单词数为2。

OK,有了以上的方法,我们只需要将他们实现成代码,就可以完成我们需要的功能了,代码如下:

#include<iostream>
#include<string>
using namespace std;

/**
Trie树中的每个节点
*/
class Node {
private:
    int count;  //计数器,每个单词插入到树中,只要经过这个节点,这个节点就计数一下
    char data;  //所存储的字符
    Node* list; //children

public:

    Node() {
        count = 0;
        data = NULL;
        list = NULL;
    }   //必须要默认的构造函数

    Node(char data) {
        count = 0;
        this->list = new Node[26];  //new是分配一个堆空间,必须手动移除
        this->data = data;

    }

    //设置数据
    void setData(char newData) {
        data = newData;
        count++;
    }

    //设置子节点的值
    void setChild(int index, char data) {
        if (list == NULL) {
            list = new Node[26];
        }

        list[index].setData(data);
    }

    //得到子节点
    Node* getChildNode(int index) {
        return &list[index];
    }

    //获取计数
    int getCount() {
        return count;
    }

    //获取数据
    char getData() {
        return data;
    }

    /*
    判断是否是结尾
    假如只存储了 abc ,这个单词,但却判断 abcd,如果不做结尾判断,判断到 c 的时候,后面就空指针了
    */
    bool isEnd() {
        return (list == NULL) ? true : false;
    }

    ~Node() {
        delete list;
    }
};

/**
Trie树
*/
class Trie {

private:
    Node* root;

public:
    Trie() {
        root = new Node();
        root->setData('a'); //随便放一个,让它的数组初始化
    }

    ~Trie() {}

    //将一个字符串插入到树中
    void insert(string str) {
        Node* last = root;  //赋个初值,后面循环的时候可以覆盖
        for (int i = 0; i < str.length(); i++) {    //遍历字符串,挨个挨个的把字符往树的相应位置上方
            last->setChild((int)str[i] - 97, str[i]);
            last = last->getChildNode((int)str[i] - 97);        //重新获取当前操作的节点,因为随着字符串往树的插入不断进行,节点也越来越深
        }
    }

    //在数中搜索一个字符串,返回以这个字符串开头的有多少个单词
    int search(string str) {
        Node* last = root;
        int result = 0;
        for (int i = 0; i < str.length(); i++) {
            if (last->isEnd()) {
                return 0;
            }
            else {
                last = last->getChildNode((int)str[i] - 97)
                result = last->getCount();
            }

        }
        return result;
    }
};
int main() {
    int n = 0;  //字典数量
    Trie* trieTree = new Trie();
    int m = 0;  //第二轮输入


    cin >> n;
    string* dic = new string[n];

    for (int i = 0; i < n; i++) {
        cin >> dic[i];
        trieTree->insert(dic[i]);
    }

    cin >> m;
    string * input = new string[m];
    for (int i = 0; i < m; i++) {
        cin >> input[i];

    }

    for (int i = 0; i < m; i++) {
        cout << trieTree->search(input[i]) << endl;
    }
}

我想每天做一个C++的算法题,来锻炼自己code c++的能力:D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值