写点什么

前缀树的增删改查

作者:擎天圣同学
  • 2023-08-20
    浙江
  • 本文字数:5331 字

    阅读完需:约 17 分钟

前缀树的增删改查

前缀树 一个很重要的数据结构 用来解决两种算法

字符串数组中,

1 (方法名: prefixNumber) 某共同前缀的个数 => 字符的后面的节点的 pass 的个数

2 (方法名: search) 某字符串的个数 hashmap 也能实现

定义:

1)单个字符串中,字符从前到后的加到一棵多叉树上

2)字符放在路上,节点上有专属的数据项(常见的是 pass 和 end 值)

3)所有样本都这样添加,如果没有路就新建,如有路就复用

4)沿途节点的 pass 值增加 1,每个字符串结束时来到的节点 end 值增加 1

/* *

可以完成前缀相关的查询要完成的功能: 前缀树节点的增删查* 一切都以增加 方法为基础 一旦这个会了 其他都是衍生* Node1数据结构 那个数组代表的意义要理解 不然会无法继续*
增加* 支撑结构, pass不为null说明有这个路径, ascii码转数字* 逻辑: 假定有个头节点root, 来一个非空的 节点来到root位置 pass++ //简单处理头节点* 遍历字符串数组, 和'a'相减转成path 如果为null 则新增一个节点 //统一处理共同的逻辑* 节点来到下一个节点, pass++* 跳出循环后 end++ //简单处理末尾节点*查找* 逻辑:* 遍历字符串数组, 和'a'相减转成path 如果为null 则返回0* 跳出循环后 给出末尾节点end*共同前缀* 逻辑:* 遍历字符串数组, 和'a'相减转成path 如果为null 则返回0* 跳出循环后 给出末尾节点pass**删除* 先用search确定是否有该路径* 和增加的逻辑反着来就好* 沿路pass--, 一旦遇到下一个节点pass--后为零 得出后面的没必要走了, 节点=null return* node.e-- //因为毕竟是一条完整的路径 必定会有这么一个节点来结尾的 除非上面发现pass只剩1了就提前结束* */
复制代码


public class Code02_TrieTree_done {    //支撑class,  pass, end 和nexts数组 用来指向后面的节点
public static class Node1 { int pass; int end; Node1[] nexts;
public Node1() { pass = 0; end = 0; nexts = new Node1[26]; // 和'a'相减得到ascii 如果nexts[path]非null 则代表存在这条路径 } }
public static class Trie1 { private Node1 root;
public Trie1() { root = new Node1(); }
public void insert(String word) { //判空 if (word == null || "" == word) return; //简单处理root节点 Node1 temp = root; temp.pass++; int path = 0; //循环处理 word=> 字符串 共同逻辑: 经过的pass++, 跳到下一个节点 for 下一个字符 char[] chars = word.toCharArray(); for (int i = 0; i < chars.length; i++) { path = chars[i] - 'a'; if (temp.nexts[path] == null) { temp.nexts[path] = new Node1(); } temp.nexts[path].pass++; temp = temp.nexts[path]; } //简单处理尾部节点end++ temp.end++; }
// word这个单词之前加入过几次 public int search(String word) { //判空 if (word == null || "" == word) return 0; //简单处理root节点 Node1 temp = root; int path = 0; //循环处理 word=> 字符串 共同逻辑: 跳到下一个节点 for 下一个字符 char[] chars = word.toCharArray(); for (int i = 0; i < chars.length; i++) { path = chars[i] - 'a'; if (temp.nexts[path] == null) { return 0; } temp = temp.nexts[path]; } return temp.end; }

// 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 public int prefixNumber(String pre) { if (pre == null || "" == pre) return 0; //简单处理root节点 Node1 temp = root; int path = 0; //循环处理 word=> 字符串 共同逻辑: 跳到下一个节点 for 下一个字符 char[] chars = pre.toCharArray(); for (int i = 0; i < chars.length; i++) { path = chars[i] - 'a'; if (temp.nexts[path] == null) { return 0; } temp = temp.nexts[path]; } return temp.pass; }
public void delete(String word) { if(search(word)==0) return; //判空 if (word == null || "" == word) return; //简单处理root节点 Node1 temp = root; temp.pass--; int path = 0; char[] chars = word.toCharArray(); for (int i = 0; i < chars.length; i++) { path = chars[i] - 'a'; if (temp.nexts[path].pass == 1) { //一旦碰到下一个节点的pass只剩1了那么说明就这一条支路 完全删除即可 temp.nexts[path] = null; return; } temp.nexts[path].pass--; temp = temp.nexts[path]; } temp.end--; }
}
public static class Node2 { public int pass; public int end; public 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 word) { if (word == null) { return; } char[] chs = word.toCharArray(); Node2 node = root; node.pass++; int index = 0; for (int i = 0; i < chs.length; i++) { index = (int) chs[i]; if (!node.nexts.containsKey(index)) { node.nexts.put(index, new Node2()); } node = node.nexts.get(index); node.pass++; } node.end++; }
public void delete(String word) { if (search(word) != 0) { char[] chs = word.toCharArray(); Node2 node = root; node.pass--; int index = 0; for (int i = 0; i < chs.length; i++) { index = (int) chs[i]; if (--node.nexts.get(index).pass == 0) { node.nexts.remove(index); return; } node = node.nexts.get(index); } node.end--; } }
// word这个单词之前加入过几次 public int search(String word) { if (word == null) { return 0; } char[] chs = word.toCharArray(); Node2 node = root; int index = 0; for (int i = 0; i < chs.length; i++) { index = (int) chs[i]; if (!node.nexts.containsKey(index)) { return 0; } node = node.nexts.get(index); } return node.end; }
// 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 public int prefixNumber(String pre) { if (pre == null) { return 0; } char[] chs = pre.toCharArray(); Node2 node = root; int index = 0; for (int i = 0; i < chs.length; i++) { index = (int) chs[i]; if (!node.nexts.containsKey(index)) { return 0; } node = node.nexts.get(index); } return node.pass; } }
public static class Right {
private HashMap<String, Integer> box;
public Right() { box = new HashMap<>(); }
public void insert(String word) { if (!box.containsKey(word)) { box.put(word, 1); } else { box.put(word, box.get(word) + 1); } }
public void delete(String word) { if (box.containsKey(word)) { if (box.get(word) == 1) { box.remove(word); } else { box.put(word, box.get(word) - 1); } } }
public int search(String word) { if (!box.containsKey(word)) { return 0; } else { return box.get(word); } }
public int prefixNumber(String pre) { int count = 0; for (String cur : box.keySet()) { if (cur.startsWith(pre)) { count += box.get(cur); } } return count; } }
// for test public static String generateRandomString(int strLen) { char[] ans = new char[(int) (Math.random() * strLen) + 1]; for (int i = 0; i < ans.length; i++) { int value = (int) (Math.random() * 6); ans[i] = (char) (97 + value); } return String.valueOf(ans); }
// for test public static String[] generateRandomStringArray(int arrLen, int strLen) { String[] ans = new String[(int) (Math.random() * arrLen) + 1]; for (int i = 0; i < ans.length; i++) { ans[i] = generateRandomString(strLen); } return ans; }
public static void main(String[] args) { int arrLen = 100; int strLen = 20; int testTimes = 100000; for (int i = 0; i < testTimes; i++) { String[] arr = generateRandomStringArray(arrLen, strLen); Trie1 trie1 = new Trie1(); Trie2 trie2 = new Trie2(); Right right = new Right(); for (int j = 0; j < arr.length; j++) { double decide = Math.random(); if (decide < 0.25) { trie1.insert(arr[j]); trie2.insert(arr[j]); right.insert(arr[j]); } else if (decide < 0.5) { trie1.delete(arr[j]); trie2.delete(arr[j]); right.delete(arr[j]); } else if (decide < 0.75) { int ans1 = trie1.search(arr[j]); int ans2 = trie2.search(arr[j]); int ans3 = right.search(arr[j]); if (ans1 != ans2 || ans2 != ans3) { System.out.println("Oops!"); } } else { int ans1 = trie1.prefixNumber(arr[j]); int ans2 = trie2.prefixNumber(arr[j]); int ans3 = right.prefixNumber(arr[j]); if (ans1 != ans2 || ans2 != ans3) { System.out.println("Oops!"); } } } } System.out.println("finish!");
}
}
复制代码


用户头像

还未添加个人签名 2019-12-01 加入

还未添加个人简介

评论

发布
暂无评论
前缀树的增删改查_算法_擎天圣同学_InfoQ写作社区