前缀树也就是字典树,Trie树
力扣上就有这么一题让你实现前缀树,咱直接看这题:208. 实现 Trie (前缀树)
Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word)
向前缀树中插入字符串 word 。
boolean search(String word)
如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix)
如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
class Trie { public Trie() { } public void insert(String word) { } public boolean search(String word) { } public boolean startsWith(String prefix) { } }
我们先来看看前缀树的结构
这里每个节点下其实是有26个分支的(当然如果不止英文字母的话可以根据自己的需求设计分支的数量,本文中的是只有英文的,也就26个分支),至于 'a'、'b'、'c' 这些字符是不需要存进节点中的,只要在节点中搞一个节点数组nexts,大小为26,那么 nexts[0]nexts[25] 就分别对应 az
节点:
static class Node { private boolean isEnd; //用于记录当前节点是否表示插入字符串的尾节点 private Node[] nexts = new Node[26]; }
代码很好理解,我们直接看代码
class Trie { static class Node { private boolean isEnd; private Node[] nexts = new Node[26]; } Node root = new Node(); public Trie() {} // 对于这道题来说,题目已经明确说了 1 <= word.length, prefix.length <= 2000 了, // 我们就不需要 校验传入参数 word 和 prefix 的合法性了 public void insert(String word) { char[] str = word.toCharArray(); Node cur = root; for (char c : str) { int index = c - 'a'; if (cur.nexts[index] == null) { cur.nexts[index] = new Node(); } cur = cur.nexts[index]; } cur.isEnd = true; } public boolean search(String word) { Node cur = goToLast(word); //return cur == null ? false : cur.isEnd; 可以下面这样简单点 return cur != null && cur.isEnd; } public boolean startsWith(String prefix) { return goToLast(prefix) != null; // 能沿着prefix走完就必然存在该前缀 } // 沿着s顺着前缀树往下走 // 返回代表s最后一个字符的节点 public Node goToLast(String s) { char[] str = s.toCharArray(); Node cur = root; for (char c : str) { int index = c - 'a'; // 如果沿着s还没走完就为null了,那直接返回null if (cur.nexts[index] == null) { return null; } cur = cur.nexts[index]; } return cur; } }
上面那种实现是一个基本的结构,当我们要实现其它一些功能(返回插入过的字符串中出现了几次 word,出现了几次 prefix,删除字符串)的时候就可以用下面这种结构了
节点:
class Node { int pass; //用于记录有多少个字符串经过了该节点 int end; //用于记录有多少个字符串以当前节点结尾 Node[] nexts = new Node[26]; } 复制代码
代码实现:
class Trie { static class Node { int pass; //用于记录有多少个字符串经过了该节点 int end; //用于记录有多少个字符串以当前节点结尾 Node[] nexts = new Node[26]; } Node root = new Node(); public Trie() {} public void insert(String word) { if (word == null || word.length() == 0) { return; } char[] str = word.toCharArray(); Node cur = root; for (char c : str) { int index = c - 'a'; if (cur.nexts[index] == null) { cur.nexts[index] = new Node(); } cur = cur.nexts[index]; cur.pass++; } cur.end++; } // 返回以前插入的word里面出现多少次当前要查的word public int searchPro(String word) { Node cur = goToLast(word); return cur == null ? 0 : cur.end; } // 返回以前插入的word里面出现多少次prefix前缀 public int startsWithPro(String prefix) { Node cur = goToLast(prefix); return cur == null ? 0 : cur.pass; } // 沿着前缀树往下走 public Node goToLast(String s) { if (s == null || s.length() == 0) { return null; } char[] str = s.toCharArray(); Node cur = root; for (char c : str) { int index = c - 'a'; if (cur.nexts[index] == null) { return null; } cur = cur.nexts[index]; } return cur; } // 实现删除功能 public void delete(String word) { if (searchPro(word) == 0) { // 如果都没有 word 的话删个毛啊 return; } Node cur = root; char[] str = word.toCharArray(); for (char c : str) { int index = c - 'a'; // 如果要去的节点pass减完后都等于0了那就没有必要再走了 if (--cur.nexts[index].pass == 0) { cur.nexts[index] = null; return; } cur = cur.nexts[index]; } cur.end--; } }
好了,今天的前缀树就到这里了,下一篇讲一个前缀树的应用:AC自动机