646-Trie字典树

Trie字典树理论

在这里插入图片描述
我们在浏览器上输入一个关键字时,浏览器都支持:当你正在输入关键字的时候,一边输入,它的下边的输入栏里就会给你自动的提醒,以你现在输入的串的前缀的所有可能出现的关键字都给你列出来了,你可以进行快速的选择,那现在有1千万个串,甚至1亿个串,如果现在输入个”abc“,怎么能够快速的把这个以abc开头的所有的关键字,串显示出来呢???
如果说是每次输个abc,再去搜这1亿个串,哪怕是用一个哈希表,都没有那么快速的,因为你要搜的是前缀,而哈希表是无序的,除非把哈希表整个搜索一遍,否则你也不知道以指定的前缀开头的单词都有哪些。而且用哈希表组织字符串也没有办法进行串排序。
我们来看看字典树,字典树和哈希表都可以进行串的快速检索,但是字典树比哈希表省空间。
因为字典树:
在这里插入图片描述

在这里插入图片描述
红色的点相当于一个字符串的结尾
这里面相当于出现了:abc,abcd,abd,bcd,efg,hi

为什么字典树进行串的快速检索比哈希表省空间呢?
因为哈希表每个串是单独存储的,而在字典树中,如果多个串的前缀是相同的,前缀只存储1遍,也就是说,所有前缀相同的串,前缀只存储1遍,省空间。
在字典树上进行快速搜索,假如说,我们去搜索“abcd",我们就从字典树的根节点开始,先找到a,然后找b,然后找c,然后找d。
很容易看出来,字典树的搜索的时间复杂度是O(m),m表示串的长度。
也是属于一种空间换时间的数据结构。

在这里插入图片描述
串的快速检索:KMP算法搜索字符,如果是多个串,串是单独的,创建哈希表查找,把字符串分割好在字典树查也可以

串排序:放到字典树,对字典树进行前序遍历,就可以得到排序好的所有的单词串。

前缀搜索:所有以ab开头的串打印出来,从b往下走的所有路径,就是了。其他3个根下的路径就不用去找了,这是哈希表做不了的。

在这里插入图片描述
在这里插入图片描述

Java版本代码实现

package com.fixbug;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * 字典树代码实现
 */
class TrieTree{

    //字典树的根节点类型
    private TrieNode root;

    public TrieTree() {//构造函数 
        //创建Trie树的根节点
        this.root = new TrieNode('\u0000', 0, new HashMap<Character, TrieNode>());
    }

    /**
     * Trie树的增加元素
     * @param word
     */
    public void add(String word){
        TrieNode cur = root;//从根节点开始遍历判断 
        for (int i = 0; i < word.length(); i++) {//遍历这个word 
            char ch = word.charAt(i);//获取word的第i个位置的字符 
            TrieNode child = cur.nodeMap.get(ch);//获取当前指向的节点的下一层中找,有没有ch字符 
            if(child == null){//表示cur节点的下一级节点,没有ch字符节点
                child = new TrieNode(ch, 0, new HashMap<Character, TrieNode>());//创建节点 
                cur.nodeMap.put(ch, child);//添加到map表里 
            }

            cur = child;//继续处理下一个节点了 
        }
        //cur就指向了word串的最后一个字符节点了
        cur.freqs++;
    }

    /**
     * Trie树的删除操作
     * @param word
     */
    public void remove(String word){
        TrieNode cur = root;//指向删除串的末尾位置 
        TrieNode del = root;//指向的cur上面的最近的freqs不为0的节点 
        char delch = word.charAt(0);

        for (int i = 0; i < word.length(); i++) {
            TrieNode child = cur.nodeMap.get(word.charAt(i));
            if(child == null){//串不存在 
                return;
            }

            //更新一下del和delch,del记录的是离cur最近的freqs不为0的字符节点
            //或者del更新到最后一个下一级字符节点不为1的节点处
            if(cur.freqs > 0 || cur.nodeMap.size() > 1){
                del = cur;
                delch = word.charAt(i);
            }

            cur = child;
        }

        if(cur.nodeMap.size() == 0){//后面没有其它字符节点了
            del.nodeMap.remove(delch);
        } else {
            cur.freqs = 0;//后面还有其它字符节点,此处不删除任何节点,只把次数写成0,代表删除操作
        }
    }

    /**
     * Trie树的查询操作
     * @param word
     * @return  找到word,返回串的次数,否则返回0
     */
    public int query(String word){
        TrieNode cur = root;//从根节点开始 
        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);//获取word的第i个位置的字符 
            TrieNode child = cur.nodeMap.get(ch);
            if(child == null){
                return 0;//没找到 
            }
            cur = child;//更新,往后走 
        }
        return cur.freqs;
    }

    /**
     * 前缀查找
     * @param prefix
     */
    public void queryPrefix(String prefix){
        TrieNode cur = root;
        for (int i = 0; i < prefix.length(); i++) {
            char ch = prefix.charAt(i);
            TrieNode child = cur.nodeMap.get(ch);
            if(child == null){
                return;
            }
            cur = child;
        }

        //cur就指向了前缀的最后一个字符了
        String word = new String(prefix.substring(0, prefix.length()-1));
        preOrder(cur, word);
    }

    /**
     * 打印Trie树,相当于是进行了串排序
     */
    public void showTrieTree(){
        String word = new String();
        preOrder(root, word);
    }

    /**
     * Trie树的前序遍历代码
     * @param root
     * @param word
     */
    private void preOrder(TrieNode root, String word) {
        if(root == null){
            return;
        }

        if(root != this.root){
            word += root.ch;
            if(root.freqs > 0){
                System.out.println(word);
            }
        }

        for(Map.Entry<Character, TrieNode> entry : root.nodeMap.entrySet()){
            if(entry.getValue() != null){
                preOrder(entry.getValue(), word);
            }
        }
    }

    /**
     * 字典树的节点类型
     */
    static class TrieNode{
        char ch;//节点存储的字符
        int freqs;//出现的次数,在每一个串的末尾字符+1,记录这个串出现的次数
        Map<Character, TrieNode> nodeMap;//存储当前节点下一层节点的信息

        public TrieNode(char ch, int freqs, Map<Character, TrieNode> nodeMap) {
            this.ch = ch;
            this.freqs = freqs;
            this.nodeMap = nodeMap;
        }
    }
}

/**
 * 字典树代码测试
 *
 */
public class TrieTestCase
{
    public static void main( String[] args )
    {
        TrieTree trie = new TrieTree();
        trie.add("hello");
        trie.add("hello");
        trie.add("hel");
        trie.add("hel");
        trie.add("hel");
        trie.add("china");
        trie.add("ch");
        trie.add("ch");
        trie.add("heword");
        trie.add("hellw");

        System.out.println(trie.query("hello"));
        System.out.println(trie.query("hel"));
        System.out.println(trie.query("china"));
        System.out.println(trie.query("ch"));

        trie.showTrieTree();
        System.out.println("搜索前缀字符串:");
        trie.queryPrefix("he");
        trie.queryPrefix("ch");

        System.out.println("-------------------------");
        trie.remove("hello");
        System.out.println(trie.query("hello"));
        System.out.println(trie.query("hel"));
        System.out.println("-------------------------");
        trie.showTrieTree();

        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 9; i++) {
            list.add(i);
        }
    }
}

在这里插入图片描述

C++实现版本

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

/**
 * 字典树代码实现
 */
struct TrieNode//节点类型
{
    TrieNode(char chg, int freqsh, unordered_map<char, TrieNode*> nodeMape)
    {
        ch = chg;
        freqs = freqsh;
        nodeMap = nodeMape;
    }
    char ch;//节点存储的字符
    int freqs;//出现的次数,在每一个串的末尾字符+1,记录这个串出现的次数
    unordered_map<char, TrieNode*>nodeMap;//存储当前节点下一层节点的信息
};

class TrieTree 
{

public:
    TrieTree()//构造函数
    {  
        root = new TrieNode('\u0000', 0, unordered_map<char, TrieNode*>());
    }

   void add(string word)//增加字符串
   {
        TrieNode* cur = root;//从根节点开始遍历判断 
        for (int i = 0; i < word.size(); i++)//遍历这个word
        {    
            char ch = word.at(i);//获取word的第i个位置的字符 
            auto it = cur->nodeMap.find(ch);
            TrieNode* child = nullptr;
            if (it != cur->nodeMap.end())
            {
                child = it->second;//获取当前指向的节点的下一层中找,有没有ch字符 
            }
            else
            {   //表示cur节点的下一级节点,没有ch字符节点,所以要创建了  
                child = new TrieNode(ch, 0, unordered_map<char, TrieNode*>());//创建节点 
                cur->nodeMap.insert({ ch, child });//添加到map表里 
            }

            cur = child;//继续处理下一个节点了 
        }
        //cur就指向了word串的最后一个字符节点了
        cur->freqs++;
   }

     void remove(string word)//删除字符串
     {
        TrieNode* cur = root;//指向删除串的末尾位置 
        TrieNode* del = root;//指向的cur上面的最近的freqs不为0的节点 
        char delch = word.at(0);

        for (int i = 0; i < word.length(); i++) 
        {
            TrieNode* child = nullptr;
            auto it= cur->nodeMap.find(word.at(i));
            if (it==cur->nodeMap.end())//串不存在 
            {   
                return;
            }
            child = it->second;
            //更新一下del和delch,del记录的是离cur最近的freqs不为0的字符节点
            //或者del更新到最后一个下一级字符节点不为1的节点处
            if (cur->freqs > 0 || cur->nodeMap.size() > 1)
            {
                del = cur;
                delch = word.at(i);
            }

            cur = child;
        }

        if (cur->nodeMap.size() == 0)
        {   //后面没有其它字符节点了
            del->nodeMap.erase(delch);
        }
        else 
        {
            cur->freqs = 0;//后面还有其它字符节点,此处不删除任何节点,只把次数写成0,代表删除操作
        }
    }


    int query(string word)//查找字符串
    {
        TrieNode* cur = root;//从根节点开始 
        for (int i = 0; i < word.size(); i++)
        {
            char ch = word.at(i);//获取word的第i个位置的字符 
            TrieNode* child = nullptr;
            auto it=  cur->nodeMap.find(ch);
            if (it==cur->nodeMap.end()) 
            {
                return 0;//没找到 
            }
            cur = child;//更新,往后走 
        }
        return cur->freqs;
    }

     void queryPrefix(string prefix)//前缀查找
     {
        TrieNode* cur = root;
        for (int i = 0; i < prefix.size(); i++)
        {
            char ch = prefix.at(i);
            TrieNode* child = nullptr;
            auto it = cur->nodeMap.find(ch);
            if (it == cur->nodeMap.end())
            {
                return;//没找到 
            }
            cur = child;
        }

        //cur就指向了前缀的最后一个字符了
        string word = string(prefix.substr(0, prefix.size() - 1));
        preOrder(cur, word);
    }


     void showTrieTree()//打印Trie树,相当于是进行了串排序
     {
        string word = string();
        preOrder(root, word);
     }

     void preOrder(TrieNode* root, string word)//Trie树的前序遍历代码
     {
        if (root == nullptr) 
        {
            return;
        }

        if (root != this->root) 
        {
            word += root->ch;
            if (root->freqs > 0)
            {
                cout << word;
            }
        }

        for (auto &entry : root->nodeMap) 
        {
            if (entry.second!=nullptr) 
            {
                preOrder(entry.second, word);
            }
        }
    }
private:
    TrieNode* root;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值