一、前缀树定义
1)单个字符串中,字符从前到后的加到一棵多叉树上
2)字符放在边上,节点上有专属的数据项(常见的是pass和end值)
3)样本增加形式,每个字符串都从根节点开始加,如果没有路就新建,如果有路就复用
4)增加时,沿途节点的pass值加1,每个字符串完结时来到的节点end值加1
作用:能够更加不便的实现前缀相干的查问
二、简版的前缀树
只能寄存26个小写字母组成的字符串
/** * 字符品种:只有26个小写字母 * * @author Java和算法学习:周一 */public static class Node1 { // 以后节点被通过了几次 private int pass; // 有多少个字符串以以后节点结尾 private int end; // 以后节点所有的子节点 private Node1[] nexts; // 节点只寄存26个小写字母 public Node1() { pass = 0; end = 0; // node[i] == null,节点不存在 nexts = new Node1[26]; }}public static class Trie1 { private Node1 root; public Trie1() { root = new Node1(); } /** * 向前缀树中增加一个字符串 */ public void insert(String word) { if (word == null) { return; } // 正在增加字符的节点 Node1 node = root; // 曾经有字符通过该节点,批改节点pass值 node.pass++; int path = 0; // 以后单词挨个字符的增加到前缀树上 char[] chars = word.toCharArray(); for (char aChar : chars) { // 以后字符应该走的门路 path = aChar - 'a'; // 以后节点的path门路对应节点不存在,则新建 if (node.nexts[path] == null) { node.nexts[path] = new Node1(); } // 以后节点的path门路对应节点必定曾经存在了 // 所以以后节点来到path门路对应节点 node = node.nexts[path]; // 曾经有字符达到该节点,批改pass值 node.pass++; } // 字符增加结束,批改最初节点的end值 node.end++; } /** * word单词之前加过多少次 */ public int search(String word) { if (word == null) { return 0; } int index = 0; char[] chars = word.toCharArray(); // 从根节点开始找 Node1 node = root; for (char aChar : chars) { // 以后字符应该走的门路 index = aChar - 'a'; // 以后节点的index门路对应节点不存在,间接返回 if (node.nexts[index] == null) { return 0; } // 以后节点的index门路对应节点存在,则以后查找节点来到index门路对应节点 node = node.nexts[index]; } // 最初以后节点的end值即是此单词加过的次数 return node.end; } /** * 所有曾经退出的字符串中,有多少是以pre为前缀的 */ public int prefixNumber(String pre) { if (pre == null) { return 0; } int index = 0; // 从根节点开始找 Node1 node = root; // 挨个字符找 char[] chars = pre.toCharArray(); for (char aChar : chars) { // 以后字符应该走的门路 index = aChar - 'a'; // 以后节点的index门路对应节点不存在,间接返回 if (node.nexts[index] == null) { return 0; } // 以后节点的index门路对应节点存在,则以后查找节点来到index门路对应节点 node = node.nexts[index]; } // 最初以后查找节点所处节点的pass值即是以pre为前缀的数量 return node.pass; } /** * 在前缀树中删除某个字符串 */ public void delete(String word) { // 字符串存在才执行删除逻辑 if (search(word) != 0) { // 从根节点开始 Node1 node = root; // 批改根节点pass值 node.pass--; int index = 0; char[] chars = word.toCharArray(); for (char aChar : chars) { // 以后字符应该走的门路 index = aChar - 'a'; // 以后节点index门路对应节点的pass值减一 if (--node.nexts[index].pass == 0) { // 减一后如果为0,表明没有字符串再通过此节点 // 将此节点index门路对应节点置空,帮忙GC node.nexts[index] = null; return; } // 减一后不为0,表明还有字符串通过此节点 // 则以后节点挪动到index门路对应的节点 node = node.nexts[index]; } // 最初批改节点所在位置end值 node.end--; } }}
三、通用的前缀树
不限定寄存的内容
/** * 字符品种:不肯定只有26个小写字母 * * @author Java和算法学习:周一 */public static class Node2 { // 以后节点被通过了几次 private int pass; // 有多少个字符串以以后节点结尾 private int end; // 以后节点所有的子节点,Key为节点对应字符串字符转换为整型后的ASCII码值 private HashMap<Integer, Node2> nexts; public Node2() { pass = 0; end = 0; nexts = new HashMap<>(); }}public static class Trie2 { private Node2 root; public Trie2() { root = new Node2(); } /** * 向前缀树中增加一个字符串,此字符串不肯定全是小写字母 */ public void insert(String str) { if (str == null) { return; } // 正在增加字符串的节点 Node2 node = root; // 曾经有字符通过该节点,批改节点pass值 node.pass++; int index = 0; // 以后字符串挨个字符的增加到前缀树上 char[] chars = str.toCharArray(); for (char aChar : chars) { index = aChar; // 以后节点的index门路对应节点不存在,则新建 if (!node.nexts.containsKey(index)) { node.nexts.put(index, new Node2()); } // 以后节点的index门路对应节点曾经存在了 // 所以以后节点来到index门路对应节点 node = node.nexts.get(index); // 曾经有字符达到该节点,批改pass值 node.pass++; } // 字符串增加结束,批改最初节点的end值 node.end++; } /** * 字符串之前加过多少次 */ public int search(String str) { if (str == null) { return 0; } int index = 0; char[] chars = str.toCharArray(); // 从根节点开始找 Node2 node = root; for (char aChar : chars) { // 以后字符应该走的门路 index = aChar; // 以后节点的index门路对应节点不存在,间接返回 if (!node.nexts.containsKey(index)) { return 0; } // 以后节点的index门路对应节点存在,则以后查找节点来到index门路对应节点 node = node.nexts.get(index); } // 最初以后节点的end值即是此字符串加过的次数 return node.end; } /** * 所有曾经退出的字符串中,有多少是以pre为前缀的 */ public int prefixNumber(String pre) { if (pre == null) { return 0; } int index = 0; // 从根节点开始找 Node2 node = root; // 挨个字符找 char[] chars = pre.toCharArray(); for (char aChar : chars) { // 以后字符应该走的门路 index = aChar; // 以后节点的index门路对应节点不存在,间接返回 if (!node.nexts.containsKey(index)) { return 0; } // 以后节点的index门路对应节点存在,则以后查找节点来到index门路对应节点 node = node.nexts.get(index); } // 最初以后节点的pass值即是以pre为前缀的数量 return node.pass; } /** * 在前缀树中删除某个字符串 */ public void delete(String str) { // 字符串存在才执行删除逻辑 if (search(str) != 0) { // 从根节点开始 Node2 node = root; // 批改根节点pass值 node.pass--; int index = 0; char[] chars = str.toCharArray(); for (char aChar : chars) { // 以后字符应该走的门路 index = aChar; // 以后节点index门路对应节点的pass值减一 if (--node.nexts.get(index).pass == 0) { // 减一后如果为0,表明没有字符串再通过此节点 // 将此节点index门路对应节点置空 node.nexts.remove(index); return; } // 减一后不为0,表明还有字符串通过此节点 // 则以后节点挪动到index门路对应的节点 node = node.nexts.get(index); } // 最初批改节点所在位置end值 node.end--; } }}
以上两种前缀树都只实现了最根本的性能,当理论我的项目中遇到相似的需要,能够在此基础上增加须要的性能即可,相当于提供了一个模板。
本文所有代码Github获取