字典树又称为前缀树或Trie树,是处理字符串常见的数据结构。假设组成所有单词的字符仅为a-z。
字典树介绍
字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间,比如加入”abc”、“abcd”、“abd”、“b”、“bcd”、“efg”、”hik”之后,字典树如图所示。
基本特性
- 根节点没有字符路径。除了根节点外,每一个节点都被一个字符路径找到。
- 从根节点到某一节点,将路径上经过的字符连接起来,为扫过的对应字符串。
- 每个节点向下所有的字符路径上的字符都不同。
在字典树上搜索添加过的单词步骤为:
- 从根节点开始搜索。
- 取得要查找单词的第一个字母,并根据该字母选择对应的字符路径向下继续搜索。
- 字符路径指向的第二层节点上,根据第二个字母选择对应的字符路径向下继续搜索。
- 一直向下搜索,如果单词搜索完后,找到的最后一个节点是一个终止节点,比如图中的实心节点,说明字典树中含有这个单词,如果找到的最后一个节点不是一个终止节点,说明单词不是字典树中添加过的单词。如果还没搜索完,但是已经没有后续节点了,也说明单词不是字典树中添加过的单词。
数据结构类型定义
public class TrieNode {
public int path;
public int end;
public TrieNode[] map;
public TrieNode() {
path = 0;
end = 0;
map = new TrieNode[26];
}
}
Variable | 用途 |
---|---|
path | 表示有多少个单词共用这个节点 |
end | 表示有多少个单词以这个节点结尾 |
map | 实质为一个哈希表结构,key代表该节点的一条字符路径,value表示字符路径指向的节点 |
注:在字符类较多的情况下,可以选择真实的哈希表结构实现map。
Trie树实现
- void insert(String word):假设word的长度为N,从左到右遍历word中的每一个字符,并以此从头节点开始根据每一个word[i],找到下一个节点。
1)如果找的过程中节点不存在,就建立新节点,记为a,并令a.path=1。
2)如果节点存在,记为b,令b.path++。
3)通过最后一个字符(word[N-1])找到最后一个节点时记为e,令e.path++,e.paht++。 - boolean search(String word):从左到右遍历word中每一个字符,并以此从头节点开始根据每一个word[i],找到下一个节点。
1)如果找的过程中节点不存在,说明这个单词的整个部分没有添加到Trie树中,否则不可能找的过程中节点不存在,直接返回false。
2)如果通过word[N-1]找到最后一个节点,记为e,如果e.end!=0,说明有单词通过word[N-1]的字符路径,并以节点e结尾,返回true;如果e.end==0,返回false; - void delete(String word):先调用search(word),看word是否在Trie树中;
1)若不在,直接返回;
2)如在,从左到右遍历word中的每个字符,并依次从头节点开始,根据每一个word[i]找到下一个的节点。在找的过程中,把扫过的每一个节点的path值减1。如果发现下一个节点的path值减完之后已经为0,直接从当前节点的map中删除后续的所有路径,返回即可。如果扫到最后一个节点,记为e,令e.path–,e.end–。 - int prefixNumber(String pre):和search操作同理,根据pre不断找到节点,假设最后的节点记为e,返回e.path的值即可。
代码实现
/**
* @Title: Trie.java
* @Package com.hf.domain
* @Description: TODO
* @author hf寒沨
* @date 2019年1月19日 下午2:51:50
* @version V1.0
*/
package com.hf.domain;
/**
* @ClassName: Trie
* @Description:
* @author hf寒沨
* @date 2019年1月19日 下午2:51:50
*
*/
public class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
/**
*
* @Title: insert
* @Description: insert word
* @param @param word
* @return void
* @throws
*/
public void insert(String word) {
if (word == null) {
return;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index] == null) {
node.map[index] = new TrieNode();
}
node = node.map[index];
node.path++;
}
node.end++;
}
/**
*
* @Title: search
* @Description: search word
* @param @param word
* @param @return
* @return boolean
* @throws
*/
public boolean search(String word) {
if (word == null) {
return false;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index] == null) {
return false;
}
node = node.map[index];
}
return node.end != 0;
}
/**
*
* @Title: delete
* @Description: delete word from Trie Tree
* @param @param word
* @return void
* @throws
*/
public void delete(String word) {
if (search(word)) {
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index].path-- == 1) {
node.map[index] = null;
return;
}
node = node.map[index];
}
node.end--;
}
}
/**
*
* @Title: prefixNumber
* @Description: count the number of word with the common prefix
* @param @param pre
* @param @return
* @return int
* @throws
*/
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.map[index] == null) {
return 0;
}
node = node.map[index];
}
return node.path;
}
}
参考文献
[1] 左程云:程序员代码面试指南