快把单词的存储都改成字典树吧

概述

​ 在计算机科学中,trie,又称前缀树或字典树。与二叉查找树不同,值不是直接保存在结点中,而是结点在树中的位置决定。一个结点的所有子孙都有相同的前缀,也就是这个结点对应的字符串,而根节点对应空字符串。

​ Trie的典型应用是用于统计,它的优点是:利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串比较。

时间复杂度

在这里插入图片描述

结点结构

每个结点都有若干向下个结点的指针

    //创建结点
    private class Node {
        boolean isWord;
        String val;
        Map<Character, Node> next;

        public Node() {
            this("");
        }

        public Node(String val) {
            this.val = val;
            this.isWord = false;
            this.next = new HashMap<>();
        }
    }
    private int size;
    private Node root;

    public Trie() {
        this.size = 0;
        this.root = new Node();
    }

    public int getSize() {
        return this.size;
    }

添加元素

将单词拆解成每个字符,然后添加到字典树中,到最后一个字符时判断那个位置是否是单词(判断isWord),如果不是,则改成是单词

    public void addStr(String str) {
        if (str.isEmpty() || str == null) {
            return;
        }
        Node cur = root;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            Map<Character, Node> children = cur.next;
            if (!children.containsKey(c)) {
                children.put(c, new Node(cur.val + c));
            }
            cur = children.get(c);
        }
        if (!cur.isWord) {
            cur.isWord = true;
            this.size++;
        }
    }

查询操作

查找是否存在指定单词

一直跟着单词往下寻找就行

    // 判断是否存在指定的单词
    public boolean matchWord(String word) {
        if (word.isEmpty() || word == null) {
            return false;
        }
        Node cur = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character, Node> children = cur.next;
            if (!children.containsKey(c)) {
                return false;
            }
            cur = children.get(c);
        }
        return cur.isWord;
    }

查找是否存在以指定前缀开始的单词

先找前缀是否存在,然后再向下判断每一个分支是否有单词

方法一

    // 判断是否存在以pre开始的单词
    public boolean matchStartPre(String pre) {
        if (pre.isEmpty() || pre == null) {
            return false;
        }
        Node cur = root;
        for (int i = 0; i < pre.length(); i++) {
            char c = pre.charAt(i);
            Map<Character, Node> children = cur.next;
            if (!children.containsKey(c)) {
                return false;
            }
            cur = children.get(c);
        }
        return true;
    }

方法二

    //查找是否存在匹配前缀的单词
    public boolean search(String express) {
        if (express == null || express.length() == 0) {
            return false;
        }
        return match(root, express, 0);
    }

    private boolean match(Node node, String express, int index) {
        if (index == express.length()) {
            return node.isWord;
        }
        char c = express.charAt(index);
        Map<Character, Node> children = node.next;
        if (c != '.') {
            if (!children.containsKey(c)) {
                return false;
            }
            return match(children.get(c), express, index + 1);
        } else {
            for (Map.Entry entry : children.entrySet()) {
                if (match((Node) entry.getValue(), express, index + 1)) {
                    return true;
                }
            }
            return false;
        }
    }

查找皮匹配前缀的所有单词

先找出前缀,然后从前缀的最后一个单词开始,从它的孩子中寻找单词,使用递归方式将所有分支都进行查找

    //查找匹配的单词
    public List<String> matchStartPreWords(String express) {
        List<String> list = new ArrayList<>();
        if (express.length() != 0 || express != null) {
            match(root, express, 0, list);
        }

        return list;
    }

    //递归把前缀去掉,前缀之后开始进双重递归找单词
    private void match(Node node, String express, int index, List<String> list) {
        if (index == express.length()) {
            findWords(node, list);
            return;
        }
        char c = express.charAt(index);
        Map<Character, Node> map = node.next;
        if (c != '.') {
            if (!map.containsKey(c)) {
                return;
            } else {
                match(map.get(c), express, index + 1, list);
            }
        } else {
            for (Map.Entry entry : map.entrySet()) {
                match((Node) entry.getValue(), express, index + 1, list);
            }
        }
    }

    //双重递归找到单词
    private void findWords(Node node, List<String> list) {
        if (node.isWord) {
            list.add(node.val);
        }
        if (node.next.size() == 0) {
            return;
        }
        Map<Character, Node> children = node.next;
        for (Map.Entry entry : children.entrySet()) {
            findWords((Node) entry.getValue(), list);
        }
    }

删除操作

1、如果单词是另外一个单词的前缀,只需要将单词的最后一个结点的isWord修改成false。

2、如果单词的所有字符都没有分支,删除整个单词

3、如果单词除了最后一个字符,其他的字符存在多个分支

4、对第二种情况的补充:如果单词的所有字符都没有分支,但是存在前缀作为单词的情况,例如:pan,panda,在删除panda时,使用第三种情况进行处理(node.next.size() == 1 && node.isword)

在这里插入图片描述

    // 删除单词
    public void deleteWord(String word) {
        if (word.length() == 0 || word == null) {
            return;
        }
        Node cur = root;
        Node mulitNode = null;
        int mulitIndex = -1;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            Map<Character,Node> children = cur.next;
            if (!children.containsKey(c)){
                return;
            }else {
                Node node = children.get(c);
                if (node.next.size()>1 || (node.isWord && node.next.size()==1)){
                    mulitNode = node;
                    mulitIndex = i;
                }
                cur = node;
            }
        }

        //真正的删除操作
        if (cur.isWord){
            if (cur.next.size()>0){
                cur.isWord = false;
            }else if (mulitNode == null){
                root.next.remove(word.charAt(0));
            }else {
                mulitNode.next.remove(word.charAt(mulitIndex+1));
            }
            this.size--;
        }

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值