数据结构:字典树

字典树类似于二叉树,内部结构并不复杂,在前缀方面百试百灵,却没有红黑树,AVL那么多变化,赶紧码起来

一、字典树介绍

在这里插入图片描述
字典树并不是死的,其内部其实是多变的,在介绍中会讲解可能的变式

1、定义结点
与二叉树一样,结点需要存储其数据,以及下一个结点的,若结点中只包含字母,我们有两种方法连接到next结点

HashMap型:Node中用hashmap来连接到下一个结点,适用于字母类型少的情况
数组型:Node中用数组来连接到下一个结点,适用于字母类型多的情况

private class Node {
	String word;
	HashMap<Character, Node> child; // hashmap型
}

解释一下word的作用:有的结点可能只作为路径存在(上图白色结点),而有的需要存储信息(上图红色结点),而存储方式可以依据需要修改

boolean:可以用于判断是否存在某个单词
String:可以直接返回找到的单词
int:可以用于记录结点被访问的次数,或如果字典中有重复的单词,可以用以记录数量

依据需要去修改结点即可,还可以尝试压缩路径

2、创建根节点
根节点是字典树的起始点,是不存储信息

Node root = new Node();

3、插入方法insert()
给一个String,将其插入到字典树中。
Stringchar一个个分离出来,利用hashmap进行插入就好,并在最后进行信息的存储

public void insert(String word) {
	Node cur = root;
	for (char c : word.toCharArray()) {
		if (!cur.child.containsKey(c))
			cur.child.put(c, new Node);
		cur = cur.child.get(c);
	}
	// 此时cur位于word最后char所在的结点
	cur.word = word;
}

4、查找方法search()
给一个String,判断是否在字典树中
方法与insert一样,将Stringchar一个个分离出来,若hashmap中没找到,或最后的结点没有存信息,则返回false

public boolean search(String word) {
	Node cur = root;
	for (char c : word.toCharArray()) {
		if (!cur.child.containsKey(c))
			return false;
		cur = cur.child.get(c);
	}
	return cur.word.equals(word);
}

当然insert()search()方法很像,可以利用形参试着合并

二、字典树实战

1、题目一:LeetCode 211 添加与搜索单词
这道题仅有一点变化,搜索的时候.可以代替单词,所以search()方法需要特殊处理
当遇到.时,需要遍历hashmap中所有的结点,所以search()使用递归
另外由于不需要返回String,所以Node存储boolean即可

private boolean search(Node cur, String word, int i) {
   	if (i == word.length())
        return cur.exist;
    char c = word.charAt(i);
    if (cur.children.containsKey(c)) {
        return search(cur.children.get(c), word, i + 1);
    } else if (c == '.') {
        for (Node child : cur.children.values())
            if (search(child, word, i + 1))
                return true;
    }
    return false;
}

2、题目二:LeetCode 212 单词搜索Ⅱ
由于要返回我们查找到的所有单词,所以Node需要以String来记录

private class Node {
    String word;
    HashMap<Character, Node> map;
    Node() {
        this.word = null;
        map = new HashMap<>();
    }
}

初始化时,添加所有单词到字典树中,insert()就不提供代码了

final int[] dx = new int[]{0, 1, 0, -1};
final int[] dy = new int[]{1, 0, -1, 0}; // 右下左上
boolean[][] visited;
char[][] board;
List<String> ans;
int rows, cols;
Node root;
public List<String> findWords(char[][] board, String[] words) {
    ans = new ArrayList<>();
    root = new Node();
    this.board = board;
    rows = board.length;
    cols = board[0].length;
    for (String s : words)
        insert(s);
}

我们采用dfs的方式展开搜索,让字典树和棋盘同步搜索

private void dfs(Node cur, int x, int y) {  
    if (cur.word != null) {
        ans.add(cur.word);
        cur.word = null; // 防止对同一单词重复添加
    }
    if (cur.map.isEmpty()) // 提前判断,剪枝
        return;
    visited[x][y] = true; 
    for (int dir = 0; dir < 4; dir++) {
        int nx = x + dx[dir];
        int ny = y + dy[dir];
        if (nx < 0 || nx >= rows || ny < 0 || ny >= cols || visited[nx][ny] || !cur.map.containsKey(board[nx][ny]))
            continue;
        dfs(cur.map.get(board[nx][ny]), nx, ny);
    }
    visited[x][y] = false; // 还原
}

以上是基本的bfs + 回溯,只不过多了字典树,多一步判断
但我们发现字典树存在冗余,考虑下面的情况
在这里插入图片描述
当我们找到aaa之后,最终的结点的数据我们已经得到了,但发现字典树的结构仍然存在,每次遍历都要到最底下的结点,发现没有数据后再一步步返回,产生大量冗余
所以我们试着去一边dfs一边删除一部分的字典树结构
当一个结点的map空了,并且没有数据了,我们便从他从父结点上将之删除,以此来去冗

private void dfs(Node cur, int x, int y) {
	// 这里的cur是父节点,child是子节点(也就是当前结点)
    Node child = cur.map.get(board[x][y]); 
    if (child.word != null) {
        ans.add(child.word);
        child.word = null;
    }
    if (child.map.isEmpty()) { // 删除部分字典树结构
        cur.map.remove(board[x][y]);
        return;
    }
    visited[x][y] = true;
    for (int dir = 0; dir < 4; dir++) {
        int nx = x + dx[dir];
        int ny = y + dy[dir];
        if (nx < 0 || nx >= rows || ny < 0 || ny >= cols || visited[nx][ny] || !cur.map.containsKey(board[nx][ny]))
            continue;
        dfs(child, nx, ny);
    }
    visited[x][y] = false; 
}

4、实战题目:
LeetCode 208 实现Trie(前缀树)
LeetCode 211 添加与搜索单词-数据结构设计
LeetCode 14 最长公共前缀
LeetCode 421 数组中两个数的最大异或值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值