数据结构 ---[实现字典树[前缀树](Trie)]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[实现字典树[前缀树](Trie)]相关的知识,希望对你有一定的参考价值。
Trie(前缀树或者字典树),主要针对于字符串的检索问题;值不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串
现在给你一本英语单词字典,要求快速查到单词zoo
,你要是一个一个地往过找,是比较耗时的;
而如果从单词的前缀开始找,先找到z
,然后接着找zo
,再接着找,就找到zoo
了;
简易做个字典树的图看看;
在这棵树中,可找到单词
app
,apple
,all
,allow
,big
,book
,dept
1.字典树添加单词
在具体实现的过程中,这个结点要有一个标记值,确认此处是否为一个单词的末尾;
在其中还要多个指向下一个存储了字符的结点;
初步完成
package com.company.segmenttree;
import java.util.HashMap;
import java.util.Map;
/**
* @author by CSDN@小智RE0
* @date 2021-11-28 16:08
*/
public class Trie
//存储的节点;
private class Node
//判断是否为单词
private boolean isWord;
//存储指向该节点下的所有字符对应的节点;
private Map<Character, Node> next;
private Node()
this.isWord = false;
this.next = new HashMap<>();
//根节点;
private Node root;
//单词的个数;
private int size;
public Trie()
this.root = new Node();
this.size = 0;
/**
* 在节点树添加新单词
*
* @param newStr 新的字符;
*/
public void buildStr(String newStr)
if (newStr == null || newStr.length() == 0)
return;
//进行添加前,新建下一个节点;
Map<Character, Node> child = root.next;
//对字符串进行循环,依次存入;
for (int i = 0; i < newStr.length(); i++)
char c = newStr.charAt(i);
//首先判断是否为第一次存入,为该map的根节点下挂接;
if (!child.containsKey(c))
Node node = new Node();
//判断是否为最后一位字符,为该字符节点标记单词标志位 isWord;
if (i == newStr.length() - 1)
node.isWord = true;
this.size += 1;
child.put(c, node);
//向后递进;
child = child.get(c).next;
测试使用
//测试;
public static void main(String[] args)
String[] strArr ="app","apple","all","allow","book","big","dept";
Trie trie = new Trie();
//将字符串存入字典树;
Arrays.stream(strArr).forEach(trie::buildStr);
System.out.println("当前字典树的单词数->"+trie.getWordSize());
结果,和存入的数量一致;
当前字典树的单词数->7
但是,若先将字符"apple"存入,再添加"app"时就会判断到单词已存在,无法添加单词"app";
"allow"和"all"也是一样的;
那么就需要对之前的添加方法进行改进;
添加方法改进
不直接操作根点,每次添加单词时都使用临时节点进行操作;’
/**
* 在节点树添加新单词
* @param newStr 新的字符;
*/
public void buildStr(String newStr)
if (newStr == null || newStr.length() == 0)
return;
//不直接操作移动根结点;而是使用一个临时节点进行操作;
Node curNode = root;
//对字符串进行循环,依次存入;
for (int i = 0; i < newStr.length(); i++)
char c = newStr.charAt(i);
Map<Character, Node> child = curNode.next;
//首先判断是否为第一次存入,为该map的根节点下挂接;
if (!child.containsKey(c))
child.put(c, new Node());
//向后递进;
curNode = child.get(c);
//对末尾字符进行标记;
if(!curNode.isWord)
curNode.isWord = true;
this.size++;
测试
public static void main(String[] args)
String[] strArr ="apple","app","allow","all","book","big","dept","big","app","apple","all";
Trie trie = new Trie();
//将字符串存入字典树;
Arrays.stream(strArr).forEach(trie::buildStr);
System.out.println("当前字典树的单词数->"+trie.getWordSize());
即使有重复单词,也不会统计次数
当前字典树的单词数->7
2.判断是否存在指定单词,判断是否存在指定前缀的单词;
/**
* 查询指定的单词是否存在
* @param destStr 指定单词
*/
public boolean searchStr(String destStr)
if(destStr==null||destStr.length()==0)
return false;
//同样用临时操作节点;
Node curNode = root;
for (int i = 0; i < destStr.length(); i++)
char c = destStr.charAt(i);
Map<Character,Node> child = curNode.next;;
//若不存在就结束;
if(!child.containsKey(c))
return false;
//向后递进;
curNode =child.get(c);
//最终还需判断走过的节点是否为一个单词;
return curNode.isWord;
/**
* 判断是否有指定前缀的单词存在;
* @param destPre 指定前缀
*/
public boolean searchPreStr(String destPre)
if(destPre==null||destPre.length()==0)
return false;
//同样用临时操作节点;
Node curNode = root;
for (int i = 0; i < destPre.length(); i++)
char c = destPre.charAt(i);
Map<Character,Node> child = curNode.next;;
//若不存在就结束;
if(!child.containsKey(c))
return false;
//向后递进;
curNode =child.get(c);
return true;
测试使用
//测试;
public static void main(String[] args)
String[] strArr ="apple","app","allow","all","book","big","dept","big","app","apple","all";
Trie trie = new Trie();
//将字符串存入字典树;
Arrays.stream(strArr).forEach(trie::buildStr);
System.out.println("当前字典树的单词数->"+trie.getWordSize());
System.out.println("单词 ap 是否存在"+trie.searchStr("ap"));
System.out.println("以前缀 ap 开头的单词是否存在"+trie.searchPreStr("ap"));
当前字典树的单词数->7
单词 ap 是否存在false
以前缀 ap 开头的单词是否存在true
3.判断是否存在指定单词(这时会将单词中的.号视为任意字母)
/**
* 判断单词是否存在; 会将.(点号) 视为任意字母;
* @param desStr 指定的单词
*/
public boolean searchSpecial(String desStr)
if(desStr == null|| desStr.length()==0)
return false;
//调用方法;从匹配字符串的第一个字符开始;
return searchSpecial(root,desStr,0);
/**
* 判断指定单词是否存在,注意单词中的.点号会表示任意字母;
* @param node 当前操作节点
* @param desStr 指定的单词
* @param i 指定单词的索引
*/
private boolean searchSpecial(Node node, String desStr, int i)
//1.递归结束条件;
if (desStr.length() == i)
return true;
//递归操作;
//用临时节点操作;
Node curNode = node;
char c = desStr.charAt(i);
//若取到的不是.号;
if (c != '.')
Map<Character, Node> child = curNode.next;
if (!child.containsKey(c))
return false;
//向后递归判断;
return searchSpecial(child.get(c), desStr, i + 1);
//若为.号;
else
Map<Character, Node> child = curNode.next;
//取到所有的键;若有一个匹配就符合;
Set<Character> characters = child.keySet();
for (Character character : characters)
if (searchSpecial(child.get(character), desStr, i + 1))
return true;
return false;
/**
* @author by CSDN@小智RE0
* @date 2021-11-28 16:08
*/
public class Trie
//存储的节点;
private class Node
//判断是否为单词
private boolean isWord;
//存储指向该节点下的所有字符对应的节点;
private final Map<Character, Node> next;
private Node()
this.isWord = false;
this.next = new HashMap<>();
//根节点;
private final Node root;
//单词的个数;
private int size;
public Trie()
this.root = new Node();
this.size = 0;
//获取单词数量;
public int getWordSize()
return this.size;
/**
* 在节点树添加新单词
*
* @param newStr 新的字符;
*/
public void buildStr(String newStr)
if (newStr == null || newStr.length() == 0)
return;
//不直接操作移动根结点;而是使用一个临时节点进行操作;
Node curNode = root;
//对字符串进行循环,依次存入;
for (int i = 0; i < newStr.length(); i++)
char c = newStr.charAt(i);
Map<Character, Node> child = curNode.next;
//首先判断是否为第一次存入,为该map的根节点下挂接;
if (!child.containsKey(c))
child.put(c, new Node());
//向后递进;
curNode = child.get(c);
//对末尾字符进行标记;
if (!curNode.isWord)
curNode.isWord = true;
this.size++;
/**
* 查询指定的单词是否存在
*
* @param destStr 指定单词
*/
public boolean searchStr(String destStr)
if (destStr == null || destStr.length() == 0)
return false;
//同样用临时操作节点;
Node curNode = root;
for (int i = 0; i < destStr.length(); i++)
char c = destStr.charAt(i);
Map<Character, Node> child = curNode.next;
;
//若不存在就结束;
if (!child.containsKey(c))
return false;
//向后递进;
curNode = child.get(c);
//最终还需判断走过的节点是否为一个单词;
return curNode.isWord;
/**
* 判断是否有指定前缀的单词存在;
*
* @param destPre 指定前缀
*/
public boolean searchPreStr(String destPre)
if (destPre == null || destPre.length() == 0)
return false;
//同样用临时操作节点;
Node curNode = root;
for (int i = 0; i < destPre.length(); i++)
char c = destPre.charAt(i);
Map<Character, Node> child = curNode.next;
;
//若不存在就结束;
if (!child.containsKey(c))
return false;
//向后递进;
curNode = child.get(c);
return true;
/**
* 判断单词是否存在; 会将.(点号) 视为任意字母;
*
* @param desStr 指定的单词
*/
public boolean searchSpecial(String desStr)
if (desStr == null || desStr.length() == 0)
return false;
//调用方法;从匹配字符串的第一个字符开始;
return searchSpecial(root, desStr, 0);
/**
* 判断指定单词是否存在,注意单词中的.点号会表示任意字母;
*
* @param node 当前操作节点
* @param desStr 指定的单词
* @param i 指定单词的索引
*/
private boolean searchSpecial(Node node, String desStr, int i)
//1.递归结束条件;
if (desStr.length() == i以上是关于数据结构 ---[实现字典树[前缀树](Trie)]的主要内容,如果未能解决你的问题,请参考以下文章
[LeetCode] 208. Implement Trie (Prefix Tree) 实现字典树(前缀树)
leetcode 208. 实现 Trie (前缀树)/字典树