Trie 字典树 Trie 字典树主要用于存储字符串,Trie 的每个 Node 保存一个字符。用链表来描述的话,一个字符串就是一个链表。每个 Node 都保存了它的所有子结点。
下图为 see、pain、panda、dog构成的字典树
结点设置
是否存在以该结点为最后字符的单词,标识位 isEnd;
当前结点的所有子节点,使用Map存储,当字符串中全为小写字母时可以用数组代替。
基本操作 插入 将一个单词的每一个字符从头到尾插入字典树,并将最后一个字符的 idEnd 标志位设为 true。
查找 从根结点开始遍历字典树,判断查找的字符串是否存在于某一条路径中,且字符串最后一个字符对应的字典树中的结点 isEnd 标志位必须为 true,代表这个结点后面没有字符。例如字典树中存在单词 panda 查找 pan 就要返回false,因为 pan 中的字符 n 对应字典树中的结点 isEnd 标志位为 false。
前缀查找 与查找类似,对最后一个字符对应字典树的结点标志位没有限制。也就是字典树中存在单词 panda,查找 pan 前缀返回true。
删除 删除操作分为三种情况:
要删除的单词位另一个单词的前缀,将最后一个字符对应字典树结点的 isEnd 标志位改为 false;
要删除的单词对应字典树中的所有 结点都 没有分支,直接删除整个链表。
要删除的单词对应字典树中的结点有分支,删除单词对应字典树链表上的最后一个分支结点到末尾结点。
第一张图中存在 panda、pan 两个单词,我们需要删除 pan ,直接将 字母 n 的 isEnd 标志位设为false。
第二张图中存在 see 这个单词,并且其链表路径上没有任何分支,直接删除整个链表。
第三张图中存在 pain、panda、pad三个单词,这三个单词在字母 a 处分叉,要删除 pad ,直接将 字符串 pad 中 a 后的字符(d)删除。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 package datastructure;import java.util.Map;import java.util.TreeMap;public class Trie { private Node root; private int size; public Trie () { root = new Node(); } public int size () { return size; } public boolean isEmpty () { return size == 0 ; } public void add (String word) { Node current = root; char [] str = word.toCharArray(); for (char c : str){ Node next = current.next.get(c); if (next == null ){ current.next.put(c, new Node()); } current = current.next.get(c); } if (!current.isEnd){ size++; current.isEnd = true ; } } public boolean contains (String word) { Node current = root; for (int i = 0 ; i < word.length(); i++){ char c = word.charAt(i); Node node = current.next.get(c); if (node == null ){ return false ; } current = node; } return current.isEnd; } public boolean containsPrefix (String prefix) { Node current = root; for (int i = 0 ; i < prefix.length(); i++){ char c = prefix.charAt(i); Node node = current.next.get(c); if (node == null ){ return false ; } current = node; } return true ; } public boolean remove (String word) { Node multiChildNode = null ; int multiChildNodeIndex = -1 ; Node current = root; for (int i = 0 ; i < word.length(); i++){ Node child = current.next.get(word.charAt(i)); if (child == null ){ return false ; } if (child.next.size() > 1 ){ multiChildNodeIndex = i; multiChildNode = child; } current = child; } if (current.next.size() > 0 ){ if (current.isEnd){ current.isEnd = false ; size-- ; return true ; } return false ; } if (multiChildNodeIndex == -1 ){ root.next.remove(word.charAt(0 )); size-- ; return true ; } if (multiChildNodeIndex != word.length() - 1 ){ multiChildNode.next.remove(word.charAt(multiChildNodeIndex + 1 )); size-- ; return true ; } return false ; } private static class Node { public boolean isEnd; public Map<Character, Node> next; public Node () { next = new TreeMap<>(); } public Node (boolean isEnd) { this .isEnd = isEnd; } } public static void main (String[] args) { Trie tree = new Trie(); tree.add("panda" ); tree.add("pan" ); tree.add("pad" ); System.out.println(tree.contains("pad" )); System.out.println(tree.contains("pand" )); System.out.println("Size: " + tree.size()); System.out.println(tree.remove("pad" )); System.out.println("Size: " + tree.size()); System.out.println(tree.remove("pan" )); System.out.println("Size: " + tree.size()); System.out.println(tree.remove("panz" )); System.out.println("Size: " + tree.size()); System.out.println(tree.remove("panda" )); System.out.println("Size: " + tree.size()); tree.add("panz" ); System.out.println(tree.contains("panz" )); } }