前缀树(Trie):又叫字典树或单词查找树,主要应用场景为给定一个字符串集合构建一棵前缀树,然后给一个字符串,判断前缀树中是否存在该字符串或者该字符串的前缀。
示意图:
其中,橘色节点为某个单词结束的节点。
数据结构(前缀树节点)
包括三个属性、构造函数、getter和setter;
三个属性分别为:
- 节点的值,即单个字符
- 该节点下属系列节点
- 该节点是否为某一个单词的结尾标志
代码如下:
//字典树节点
class TrieTreeNode {
//节点值
private Character value;
//该节点下属系列节点
private HashMap<Character, TrieTreeNode> nexts;
//该节点是否为某一个单词的结尾标志
private boolean endNodeFlag;
//初始化
public TrieTreeNode(Character value) {
this.value = value;
nexts = new HashMap<>();
this.endNodeFlag = false;
}
//getter和setter
public HashMap<Character, TrieTreeNode> getNexts() {
return nexts;
}
public boolean isEndNodeFlag() {
return endNodeFlag;
}
public void setEndNodeFlag(boolean endNodeFlag) {
this.endNodeFlag = endNodeFlag;
}
}
前缀树的常用操作
- 插入:向前缀树中插入单词,构建前缀树
- 查找:判断前缀树中是否存在目标字符串
- 匹配:判断前缀树中是否存在与目标字符串匹配的前缀字符串
插入
最初,只有节点root,其下属节点不用初始化
代码如下:
//插入
public void insert(String word) {
//当前节点
TrieTreeNode nowNode = this.root;
char[] chs = word.toCharArray();
for (char c : chs) {
//当前节点的下属节点中不包含c,就创建新的节点
if (!nowNode.getNexts().containsKey(c)) {
//创建下一个节点
TrieTreeNode newNode = new TrieTreeNode(c);
//将新的节点放入nownode下属节点中
nowNode.getNexts().put(c, newNode);
}
//更新nownode到下一层
nowNode = nowNode.getNexts().get(c);
}
//设置单词结束标志
nowNode.setEndNodeFlag(true);
}
查找
两个步骤:
- 首先看看某一节点的下属节点是否存在路径
- 其次看看终点处的节点是否有效
代码如下:
//查找
public boolean search(String word) {
//当前节点
TrieTreeNode nowNode = this.root;
char[] chs = word.toCharArray();
for (char c : chs) {
//当前节点的下属节点中包含c,就继续更新nownode,否则没有,返回false
if (nowNode.getNexts().containsKey(c)) {
nowNode = nowNode.getNexts().get(c);
} else {
return false;
}
}
//遍历结束后,判断当前节点是否为单词结束节点
return nowNode.isEndNodeFlag();
}
匹配
与【查找】差不多,但也有不同:
- 查找:必须遍历到目标字符串的末尾,然后再判断路径是否有效
- 匹配:只要在遍历的过程有中,下属节点存在,即可继续遍历,否则不存在
代码如下:
//匹配 与查找只有一点点不同,不用判断单词结束节点
public boolean startsWith(String prefix) {
TrieTreeNode nowNode = this.root;
char[] chs = prefix.toCharArray();
for (char c : chs) {
//当前节点的下属节点中包含c,就继续更新nownode,否则没有,返回false
if (nowNode.getNexts().containsKey(c)) {
nowNode = nowNode.getNexts().get(c);
} else {
return false;
}
}
return true;
}
完整代码
import java.util.HashMap;
/**
* 主要三个方法
* <p>
* 插入
* 查找
* 匹配
*/
//字典树节点
class TrieTreeNode {
//节点值
private Character value;
//该节点下属系列节点
private HashMap<Character, TrieTreeNode> nexts;
//该节点是否为某一个单词的结尾标志
private boolean endNodeFlag;
//初始化
public TrieTreeNode(Character value) {
this.value = value;
nexts = new HashMap<>();
this.endNodeFlag = false;
}
//getter和setter
public HashMap<Character, TrieTreeNode> getNexts() {
return nexts;
}
public boolean isEndNodeFlag() {
return endNodeFlag;
}
public void setEndNodeFlag(boolean endNodeFlag) {
this.endNodeFlag = endNodeFlag;
}
}
public class TrieTree {
//根节点值
Character rootValue = '$';
//根节点
TrieTreeNode root;
//初始化
public TrieTree() {
root = new TrieTreeNode(rootValue);
}
//插入
public void insert(String word) {
//当前节点
TrieTreeNode nowNode = this.root;
char[] chs = word.toCharArray();
for (char c : chs) {
//当前节点的下属节点中不包含c,就创建新的节点
if (!nowNode.getNexts().containsKey(c)) {
//创建下一个节点
TrieTreeNode newNode = new TrieTreeNode(c);
//将新的节点放入nownode下属节点中
nowNode.getNexts().put(c, newNode);
}
//更新nownode到下一层
nowNode = nowNode.getNexts().get(c);
}
//设置单词结束标志
nowNode.setEndNodeFlag(true);
}
//查找
public boolean search(String word) {
//当前节点
TrieTreeNode nowNode = this.root;
char[] chs = word.toCharArray();
for (char c : chs) {
//当前节点的下属节点中包含c,就继续更新nownode,否则没有,返回false
if (nowNode.getNexts().containsKey(c)) {
nowNode = nowNode.getNexts().get(c);
} else {
return false;
}
}
//遍历结束后,判断当前节点是否为单词结束节点
return nowNode.isEndNodeFlag();
}
//匹配 与查找只有一点点不同,不用判断单词结束节点
public boolean startsWith(String prefix) {
TrieTreeNode nowNode = this.root;
char[] chs = prefix.toCharArray();
for (char c : chs) {
//当前节点的下属节点中包含c,就继续更新nownode,否则没有,返回false
if (nowNode.getNexts().containsKey(c)) {
nowNode = nowNode.getNexts().get(c);
} else {
return false;
}
}
return true;
}
}