Trie字典树

一)Trie字典树简介

Trie字典树又可以称为前缀树。

典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

 

二)Trie字典树模型

该模型就没有用具体的单词创建了,随便定义了一些错误单词。

String[] words = {"abcd", "A", "Accd", "ab", "B", "ccc", "abe", "abf", "abhh"};

备注:橘黄色的节点表示一个完整的单词尾部。

 

三)Trie字典树

原理:利用Map中的TreeMap存储字典树的节点信息。

第一步:定义Trie字典树结构

import java.util.Map;
import java.util.TreeMap;

/**
 * Trie字典树
 * @author ouyangjun
 */
public class TrieTree {
	
    // 字典类
    static class Node {
        boolean isWord; // 标识是否属于一个完整的词语
        Character key;  // 节点的值, 作为子节点的key
        int prefixNum;  // 表示有多少个词, 以此单词为前缀
        Map<Character, Node> next; // 节点的所有子节点,用Map来保存 (Map next)
		
        Node() {
            next = new TreeMap<Character, Node>();
        }
		
        Node(boolean isWord, Character key, int prefixNum) {
            this();
            this.isWord = isWord;
            this.key = key;
            this.prefixNum = prefixNum;
        }
    }
	
    private Node root; // 根节点
    private int size;  // 节点数量
	
    // 初始化
    public TrieTree() {
        root = new Node();
    }
	
    // 获取根节点
    public Node getRoot() {
        return root;
    }

    // 获取节点数量
    public int size() {
        return size;
    }
	
    // 判断节点是否为空
    public boolean isEmpty() {
        return size()==0;
    }
}

 

第二步:新增词语

说明:先把单词转换成字节数组,再判断每一个字符是否已经存在,如不存在,则属于新增的节点,如字符已存在,则不处理。

// 新增节点
public void add(String word) {
    if (word == null || word.length()==0) {
        return;
    }
		
    Node node = root;
    char[] ch = word.toCharArray(); // 转换成char
    for (char c : ch) {
        // 判断节点是否存在
        Node child = node.next.get(c);
        if (child == null) {
            // 新增节点
            node.next.put(c, new Node(false, c, 0));
        }
        node.next.get(c).prefixNum++;
        node = node.next.get(c); // 查找下一个子节点
    }
		
    // 如果当前的node已经是一个word,则不需要添加
    if (!node.isWord) {
        size++;
        node.isWord = true;
    }
}

 

第三步:查找词语是否存在

说明:遍历带查找的字符串的字符,如果每个节点都存在,并且待查找字符串的最后一个字符对应的Node的isWord属性为 true,则表示该单词存在。

// 查找单词是否存在
public boolean search(String word) {
    if (word == null || word.length()==0) {
        return false;
    }
		
    Node node = root;
    char[] ch = word.toCharArray(); // 转换成char
    for (char c : ch) {
        // 判断节点是否存在
        Node child = node.next.get(c);
        if (child == null) {
            return false;
        }
        node = child;
    }
    return node.isWord;
}

 

第四步:根据词语的前缀查找是否存在

// 根据词语的前缀查找是否存在
public int containsPrefix(String word) {
    if (word == null || word.length()==0) {
        return 0;
    }
		
    Node node = root;
    char[] ch = word.toCharArray(); // 转换成char
    for (char c : ch) {
        // 判断节点是否存在
        Node child = node.next.get(c);
        if (child == null) {
            return 0;
        }
        node = child;
    }
    // 返回数量
    return node.prefixNum;
}

 

第五步:删除词语

场景一如果单词是另一个单词的前缀,只需要把该word的最后一个节点的isWord的改成false。

场景二如果单词的所有字母的都没有多个分支,删除整个单词。

场景三:如果单词的除了最后一个字母,其他的字母有多个分支。

// 删除词语
public boolean remove(String word) {
    if (word == null || word.length()==0) {
        return false;
    }
		
    Node childNode = null;
    int childNodeIndex = -1;
		
    Node node = root;
    char[] ch = word.toCharArray(); // 转换成char
    for (int i=0; i<ch.length; i++) {
        // 判断节点是否存在
        Node child = node.next.get(ch[i]);
        if (child == null) {
            return false;
        }
			
        // 当前节点的子节点大于1个,记录是否有多个分支
        if (child.next.size() > 1) {
            childNodeIndex = i;
            childNode = child;
        }
			
        node = child;
    }
		
    // 场景一: 如果单词是另一个单词的前缀,只需要把该word的最后一个节点的isWord的改成false
    if (!node.next.isEmpty() && node.isWord) {
        node.isWord = false;
        size--;
        return true;
    }
		
    // 场景二: 如果单词的所有字母的都没有多个分支,删除整个单词
    if (childNodeIndex == -1) {
         root.next.remove(ch[0]);
        size--;
        return true;
    }
		
    // 场景三: 如果单词的除了最后一个字母,其他的字母有多个分支
    if (childNodeIndex != ch.length - 1) {
        childNode.next.remove(ch[childNodeIndex + 1]);
        size--;
        return true;
    }
    return false;
}

 

第六步:根据字典树还原词语,相当于字典树遍历

// 根据字典树还原词语,相当于字典树遍历
public void print() {
    Node node = root;
    // 打印
    print(node, "", false);
}
	
private void print(Node node, String word, boolean isWord) {
    // 判断节点是否为空
    if (node != null) {
        // 只输出有效的完整单词,无效的不需要输出
        if (isWord) {
            System.out.print(word + "\t"); // 可以把所有单词存储起来,方便“检验单词是否包含功能”使用
        }
			
        // 递归所有的单词,并查找是否存在,并循环
        for (Map.Entry<Character,TrieTree.Node> entry : node.next.entrySet()) {
            Node n = node.next.get(entry.getKey());
            //System.out.println(n.key + "(" + n.isWord+ "\t:" + n.prefixNum+")" + n.next);
				
            print(n, word + entry.getKey(), n.isWord);
        }
    }
}

 

第七步:main方法测试

public static void main(String[] args) {
    TrieTree trie = new TrieTree();
		
    // 构建Trie字典树
    String[] words = {"abcd", "A", "Accd", "ab", "B", "ccc", "abe", "abf", "abhh"};
    for(String word : words) {
        trie.add(word);
    }
		
    System.out.println("字典树构建之后所有的单词: ");
    trie.print();
		
    String word = "abc";
    System.out.println("\n\n单词"+word+"是否存在: " + trie.search(word));
		
    word = "abf";
    System.out.println("单词"+word+"是否存在: " + trie.search(word));
		
    word = "ab";
    System.out.println("前缀"+word+"的单词、有多少个单词以它为前缀: " + trie.containsPrefix(word));
		
    word = "A";
    System.out.println("单词"+word+"是否删除: " + trie.remove(word));
		
    word = "ccc";
    System.out.println("单词"+word+"是否删除: " + trie.remove(word));
		
    word = "abdd";
    System.out.println("单词"+word+"是否删除: " + trie.remove(word));
		
    word = "abe";
    System.out.println("单词"+word+"是否删除: " + trie.remove(word));
		
    System.out.println("\n删除之后,字典树中所有的单词: ");
    trie.print();
}

打印效果图:

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值