高级数据结构 - 前缀树

高级数据结构 - 前缀树

重要性质

​ 每个节点至少包含两个基本属性

  • children:数组或者集合,罗列出每个分支当中保安的所有字符
  • isEnd:布尔值,表示该节点是否为某字符串的结尾

​ 根节点是空的

​ 除了根节点,其他所有节点都有可能是单词的结尾,叶子节点一定都是单词的结尾

在这里插入图片描述

案例1:LeetCode 208 实现Trie(前缀树)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true
说明:

你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

class Trie {
    private Trie[] children = new Trie[26];
    //是否为最后一个节点
    private boolean isEnd = false;

    /** Initialize your data structure here. */
    public Trie() {

    }
    
    //添加字符串进入前缀树
    public void insert(String word) {
        Trie root = this;
        char[] c = word.toCharArray();
        for(int i = 0; i < c.length; i++){
            //如果当前root节点没有这个子节点,则将当前字符加入子节点中
            if(root.children[c[i] - 'a'] == null){
                root.children[c[i] - 'a'] = new Trie();
            }
            //更新root节点
            root = root.children[c[i] - 'a'];
        }
        root.isEnd = true;
    }
    
    //搜索
    public boolean search(String word) {
        Trie root = this;
        char[] c = word.toCharArray();
        for(int i = 0; i < c.length; i++){
            //如果不存在子节点,意味着已经不匹配了,直接返回false
            if(root.children[c[i] - 'a'] -= null){
                return false;
			}
             root = root.children[c[i] - 'a'];
        //最后还要记得判断是不是最后一个节点
        return root.isEnd ? true : false;
    }
    
    //前缀匹配
    public boolean startsWith(String prefix) {
        Trie root = this;
        char[] c = prefix.toCharArray();
        for(int i = 0; i < c.length; i++){
            if(root.children[c[i] - 'a'] == null){
                return false;
            }
            root = root.children[c[i] - 'a'];
        }
        return true;
    }
}

案例2:LeetCode 212 单词搜索

给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例:

输入: 
words = ["oath","pea","eat","rain"] and board =
[
  ['o','a','a','n'],
  ['e','t','a','e'],
  ['i','h','k','r'],
  ['i','f','l','v']
]

输出: ["eat","oath"]
说明:
你可以假设所有输入都由小写字母 a-z 组成。

提示:

你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。
    
解题:
    这里的前缀树实现和上面的前缀树道理相同,唯一不同的是上面的前缀树使用布尔值记录是否是最终节点,而本题用的是String类型,为的是记录整个字典的值,方便添加在返回结果集中。

class Solution {
    private int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
    private boolean[][] isChooes;
    private List<String> res;
    public List<String> findWords(char[][] board, String[] words) {
        // 1、将字典添加到前缀树
        Trie root = new Trie();
        for(String word : words){
            Trie cur = root;
            char[] c = word.toCharArray();
            for(int i = 0; i < c.length; i++){
                cur = cur.append(c[i]);
            }
            //单词已经结束,加入前缀树节点中
            cur.word = word;
        }
        // 2、初始化结果集
        res = new ArrayList<String>();
        isChooes = new boolean[board.length][board[0].length];
        // 3、深度优先搜素
        for(int i = 0; i < board.length; i++){  
            //对board每一个点都进行检索
            for(int j = 0; j < board[i].length; j++){
                dfs(root,i,j,board);
            }
        }
        return res;
    }
    public void dfs (Trie cur,int x,int y,char[][] board){
        //边界返回
        if(x < 0 || y < 0 || x >= board.length || y >= board[0].length || isChooes[x][y]){
            return;
        }
        isChooes[x][y] = true;
        cur = cur.children[board[x][y] - 'a'];
        if(cur != null){
            if(cur.word != null){
                res.add(cur.word);
                // 清除节点值,防止后面重复添加该节点
                cur.word = null;
            }
            for(int[] direction : directions){
                dfs(cur, x + direction[0], y + direction[1], board);
            }
        }
        // 回溯
        isChooes[x][y] = false;
    }
    
    //前缀树实现
    class Trie {
        private Trie[] children = new Trie[26];
        //最后一个节点,记录之前整个单词
        private String word;

       public Trie append(char ch){  //拼接字符
            if(children[ch-'a'] != null){ 
                return children[ch-'a']; //有则直接返回节点
            }
            children[ch-'a'] = new Trie(); //没有就新建节点
            return children[ch-'a'];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值