当前位置: 首页 > news >正文

前缀树的设计与实现

🚀 优质资源分享 🚀

学习路线指引(点击解锁)知识定位人群定位
🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

前缀树的设计与实现

作者:Grey

原文地址:

博客园:前缀树的设计与实现

CSDN:前缀树的设计与实现

前缀树即字典树,可以利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。

我们使用搜索引擎的时候,输入 test,后面会自动显示一堆前缀是 test 的东西。这就利用了前缀树结构来实现。

比如我们有一堆字符串

["Trie","apple", "insert","apple", "search", "app","search", "startsWith", "insert", "search"]

问题1.查询字符串列表里面是否有以appapx为前缀的字符串。

问题2.查询字符串列表里面有没有insertserac这两个字符串。

我们可以把字符串列表构造成一棵前缀树,如下图

image

头节点是空串,路径表示字符,节点表示一个字符串的前缀(简称 p 节点),黑色节点表示一个字符串的结尾位置(简称 e 节点)。

有了如上结构,针对问题1,判断是否存在app前缀的字符串,流程如下

头节点开始,先看有没有走向a的路径,有,则移动指针往a的路径走,
然后判断是否有走向p的路径,有,则移动指针往p的路径走,
然后判断是否有走向p的路径,有,则移动指针往p的路径走,

则存在以app为前缀的字符串。

同理,回到头节点,继续判断apx,发现到没有到x的路径,则直接返回不存在以apx为前缀的字符串。

针对问题2,判断是否有insert这个字符串,流程和判断前缀的流程一样,只不过到了末尾位置,判断是否是黑色节点,如果是黑色节点,说明存在这样的字符串,否则不存在。

更进一步,前缀树也可以支持加入元素和删除元素,此时,我们需要在 p 节点和 e 节点记录一个出现次数的信息,如上例,记录次数后,前缀树如下图

image

如果要删除一个apple字符串,前缀树在apple的路径上依次减一即可

image

如果要继续增加一个apx字符串,前缀树继续建出apx的路径即可。

image

依据上述流程,我们可以用 Hash 表实现前缀树,代码如下

import java.util.HashMap;

public class Code\_0030\_TrieTree {

    public static class Node2 {
        // 某个节点经历了几次
        public int pass;
        // 某个节点是否是结尾位置
        public int end;
        // 是否有走向某个节点的路
        public HashMap<Integer, Node2> nexts;

        public Node2() {
            pass = 0;
            end = 0;
            nexts = new HashMap<>();
        }
    }

    public static class Trie2 {
        private Node2 root;

        public Trie2() {
            root = new Node2();
        }

        public void insert(String word) {
            if (word == null || word.length() < 1) {
                return;
            }
            char[] str = word.toCharArray();
            Node2 cur = root;
            cur.pass++;
            int n = 0;
            for (char v : str) {
                n = v;
                if (!cur.nexts.containsKey(n)) {
                    cur.nexts.put(n, new Node2());
                }
                cur.nexts.get(n).pass++;
                cur = cur.nexts.get(n);
            }
            cur.end++;
        }

        public void delete(String word) {
            if (search(word) == 0) {
                return;
            }
            char[] str = word.toCharArray();
            Node2 cur = root;
            cur.pass--;
            for (char v : str) {
                int n = v;
                if (--cur.nexts.get(n).pass == 0) {
                    cur.nexts.remove(n);
                    return;
                }
                cur = cur.nexts.get(n);
            }
            cur.end--;
        }

        // word这个单词之前加入过几次
        public int search(String word) {
            if (word == null || word.length() < 1) {
                return 0;
            }
            char[] str = word.toCharArray();
            Node2 cur = root;
            for (char v : str) {
                int n = v;
                if (!cur.nexts.containsKey(n)) {
                    return 0;
                }
                cur = cur.nexts.get(n);
            }
            return cur.end;
        }

        // 所有加入的字符串中,有几个是以pre这个字符串作为前缀的
        public int prefixNumber(String pre) {
            if (pre == null || pre.length() < 1) {
                return 0;
            }
            char[] str = pre.toCharArray();
            Node2 cur = root;
            for (char v : str) {
                int n = v;
                if (!cur.nexts.containsKey(n)) {
                    return 0;
                }
                cur = cur.nexts.get(n);
            }
            return cur.pass;
        }
    }
}


如果字符串只由 26 个英文字母组成,那么前缀树的数据结构可以优化成

        class Node {
            int p;
            int e;
            Node[] nodes = new Node[26];
        }

nodes[0] != null:表示有走向a的路,否则则没有走向a的路;

nodes[1] != null:表示有走向b的路,否则则没有走向b的路;

nodes[2] != null:表示有走向c的路,否则则没有走向c的路;

nodes[25] != null:表示有走向z的路,否则则没有走向z的路;

LeetCode 有对应的题目链接:见:LeetCode 208. Implement Trie (Prefix Tree)

完整代码如下

class Trie {
        class Node {
            int p;
            int e;
            Node[] nodes = new Node[26];
        }

        Node root;

        public Trie() {
            root = new Node();
        }

        public void insert(String word) {
            char[] str = word.toCharArray();
            Node cur = root;
            for (char c : str) {
                cur.p++;
                if (cur.nodes[c - 'a'] == null) {
                    cur.nodes[c - 'a'] = new Node();
                }
                cur = cur.nodes[c - 'a'];
            }
            cur.e++;
        }

        public boolean search(String word) {
            char[] str = word.toCharArray();
            Node cur = root;
            for (char c : str) {
                if (cur.nodes[c - 'a'] == null) {
                    return false;
                }
                cur = cur.nodes[c - 'a'];
            }
            return cur.e != 0;
        }

        public boolean startsWith(String prefix) {
            char[] str = prefix.toCharArray();
            Node cur = root;
            for (char c : str) {
                if (cur.nodes[c - 'a'] == null) {
                    return false;
                }
                cur = cur.nodes[c - 'a'];
            }
            return true;
        }
    }


本文的所有图例见: processon:前缀树的设计和实现

更多

算法和数据结构笔记

相关文章:

  • 互联网中常见的随机数+分布式中常见的防止重复处理的方式
  • docker-compose
  • css实现input搜索框展开动画
  • 8.城市交通
  • 软件流程和管理(六):Project Scheduling
  • 2022-09-01 mysql/stonedb-多线程并行遍历元组遇到的问题分析
  • MATLAB | 面积图、饼状图、水平柱状图的斜线填充(阴影填充)
  • IDEA开发环境初始化配置
  • 企业单位公众号如何上传附件(如Word,Excel,PPT等)
  • Java中二维数组练习题
  • 一步到位,在Ubuntu中开启MySQL Windows Navicat能远程访问
  • 关于 Math.random()生成指定范围内的随机数的公式推导
  • 抛砖系列之git仓库拆分工具git-filter-repo
  • 基于51单片机温度监控Proteus仿真设计_报警值可调
  • 海关 瑞数5.5 找后缀加密入口解析
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • HTTP 简介
  • java概述
  • Redis 懒删除(lazy free)简史
  • 初识 webpack
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 翻译:Hystrix - How To Use
  • 利用DataURL技术在网页上显示图片
  • 前端面试总结(at, md)
  • 区块链将重新定义世界
  • 数组大概知多少
  • 新书推荐|Windows黑客编程技术详解
  • 自制字幕遮挡器
  • Spring第一个helloWorld
  • 阿里云移动端播放器高级功能介绍
  • ​520就是要宠粉,你的心头书我买单
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • $L^p$ 调和函数恒为零
  • $refs 、$nextTic、动态组件、name的使用
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (27)4.8 习题课
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (二)windows配置JDK环境
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)计算机毕业设计高校学生选课系统
  • (十三)Maven插件解析运行机制
  • .gitignore文件—git忽略文件
  • .NET : 在VS2008中计算代码度量值
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET Core 中的路径问题
  • .NET 常见的偏门问题
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .net和php怎么连接,php和apache之间如何连接
  • .net开发时的诡异问题,button的onclick事件无效
  • .NET开源快速、强大、免费的电子表格组件
  • .NET委托:一个关于C#的睡前故事
  • @property python知乎_Python3基础之:property
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题
  • [ C++ ] template 模板进阶 (特化,分离编译)