前言
字典树又称单词查找树,它是一种树形结构,是一种哈希树的变种,典型应用是用于统计,保存大量的字符串(但不仅限于字符串),统计以是否有以某字符串最为前缀的字符串,有的话有多少,某字符串出现了多少次等,所以经常被搜索引擎系统用于文本词频统计。
它与字典很相似,当你要查一个单词是不是在字典树中,首先看单词的第一个字母是不是在字典的第一层,如果不在,说明字典树里没有该单词,如果在就在该字母的孩子节点里找是不是有单词的第二个字母,没有说明没有该单词,有的话用同样的方法继续查找.字典树不仅可以用来储存字母,也可以储存数字等其它数据。它的优势是,利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表还高,当数据足够庞大时,会发现她比传统的字符串统计要快很多。
它有三个基本性质:
(1)根节点不存储字符
(2)除根节点外每一个节点都只存储一个字符
(3)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串,每个节点的所有子节点包含的字符都不相同。
下边我将把我实现的代码和大家分享一下,代码几乎每行都有详细注释,大家一看就清除明了了,时间原因,就不再用多余的文字再追加详述了。
创建字典树
- package Trie;
-
- import org.apache.commons.lang3.StringUtils;
-
- import com.google.common.base.CharMatcher;
-
-
-
-
-
-
- public class Trie {
-
-
- private final int SIZE = 26;
-
- private int numNode;
-
- private int depth;
-
- private TrieNode root;
-
-
-
-
- public Trie() {
- this.numNode=0;
- this.depth=0;
- this.root = new TrieNode();
- }
-
-
-
-
- private class TrieNode {
-
-
- private TrieNode[] son;
-
- private int numPass;
-
- private int numEnd;
-
- private boolean isEnd;
-
- private char value;
-
-
-
-
- public TrieNode() {
- this.numPass=0;
- this.numEnd=0;
- this.son = new TrieNode[SIZE];
- this.isEnd = false;
- }
- }
首先各种方法操作方法的字符串验证或非法判断
一般字典树常用存储单词类字符串,当然也可以存储数字或者其他字符只是一个耗费内存较多的问题,不管怎么最好在操作方法之前做个验证和判断,以更加的提高操作效率,代码健壮性更强,如插入,查找是否存在等,如果字符串中存在“非法”的字符,那么可以直接返回false来结束操作。代码如下边所示:
-
-
-
- private boolean isStrOfLetter(String str){
-
- if (StringUtils.isBlank(str)){
- return false;
- }
-
- if(!CharMatcher.JAVA_LETTER.matchesAllOf(str)){
- return false;
- }
- return true;
- }
其中代码中我用到了common-lang工具包里的StringUtils工具类和Guava里的CharMatcher,当然大家可以用循环啊用正则表达式或者其他工具来实现相同的功能,这里就不再一一详述了,如果有想深入了解它们用法的话或jar包下载,可参考我的以下博文:commons-lang中常用方法,StringUtils方法全集介绍,JavaScript、Java正则表达式详解,打了兴奋剂的CharMatcher,Strings字符串判断工具。
字典树存储字符串方法实现
-
-
-
- public boolean insertStr(String str) {
-
- if(!isStrOfLetter(str)){
- return false;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] == null) {
- node.son[pos] = new TrieNode();
- node.son[pos].value = c;
- node.son[pos].numPass=1;
- this.numNode++;
- } else {
- node.son[pos].numPass++;
- }
- node = node.son[pos];
- }
- node.isEnd = true;
- node.numEnd++;
- if(letters.length>this.depth){
- this.depth=letters.length;
- }
-
- return true;
- }
查找是否存以某前缀开头的字符串方法实现
-
-
-
- public boolean isContainPrefix(String str) {
-
- if(!isStrOfLetter(str)){
- return false;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] != null) {
- node=node.son[pos];
- } else {
- return false;
- }
- }
- return true;
- }
查找是否存在某字符串(不为前缀)方法实现
-
-
-
- public boolean isContainStr(String str) {
-
- if(!isStrOfLetter(str)){
- return false;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] != null) {
- node=node.son[pos];
- } else {
- return false;
- }
- }
- return node.isEnd;
- }
统计以指定字符串为前缀的字符串数量方法实现
-
-
-
- public int countPrefix(String str) {
-
- if(!isStrOfLetter(str)){
- return 0;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] == null) {
- return 0;
- } else {
- node=node.son[pos];
- }
- }
- return node.numPass;
- }
统计某字符串出现的次数方法实现
-
-
-
- public int countPrefix(String str) {
-
- if(!isStrOfLetter(str)){
- return 0;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] == null) {
- return 0;
- } else {
- node=node.son[pos];
- }
- }
- return node.numPass;
- }
前序遍历字典树方法实现
-
-
-
- public int countPrefix(String str) {
-
- if(!isStrOfLetter(str)){
- return 0;
- }
-
- str=str.toLowerCase();
- char[] letters = str.toCharArray();
- TrieNode node=this.root;
- for (char c:letters) {
- int pos = c - 'a';
- if (node.son[pos] == null) {
- return 0;
- } else {
- node=node.son[pos];
- }
- }
- return node.numPass;
- }
这里是通过递归打印出所有的节点的值,如果想存入一个List或者追加StringBuilder或者StringBuffer中,需要创建一个全局变量或者方法里创建然后以参数形式传到递归方法中,这里不再进行详述,因为字典树的主要用途不在这里,此方法一般不需要。
返回字典树的根节点
-
-
-
- public TrieNode getRoot() {
- return this.root;
- }
返回字典树的深度
-
-
-
- public int getDept() {
- return this.depth;
- }
返回字典树的所有子节点的数目
-
-
-
- public int getNumNode() {
- return this.numNode;
- }
测试字典树的所有方法
- package Trie;
-
- import org.junit.Test;
-
- public class TrieTest {
-
-
-
-
-
-
- @Test
- public void testTrie(){
-
- Trie trie=new Trie();
-
- String[] testStrs=new String[]{"chefsd","chen","hahi","ch","cxing","hahha","my","home"};
- for(String s:testStrs){
- trie.insertStr(s);
- }
-
-
- boolean isCont=trie.isContainPrefix("ch");
- System.out.println(isCont);
-
-
- int countPrefix=trie.countPrefix("ch");
- System.out.println(countPrefix);
-
-
- int countStr=trie.countStr("ch");
- System.out.println(countStr);
-
-
- int countPre=trie.countPrefix("chee");
- System.out.println(countPre);
-
-
- int numNode=trie.getNumNode();
- int dept=trie.getDept();
- System.out.println("字典树子节点的数量:"+numNode+" 树的深度:"+dept);
- }
- }
测试结果
- true
- 3
- 1
- 0
- 字典树子节点的数量:22 树的深度:6
over了!
转载请注明—作者:Java我人生(陈磊兴) 原文出处:http://blog.csdn.net/chenleixing/article/details/44708533
最后,认真看过的网友们,大神们,如有感觉我这个程序猿有哪个地方说的不对或者不妥或者你有很好的提议或者建议或点子方法,还望您大恩大德施舍n秒的时间留下你的宝贵文字(留言),以便你,我,还有广大的程序猿们更快地成长与进步.......