Trie树分析

Trie树

Trie树介绍

Trie,又称单词查找树或键树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

它有3个基本性质:

1.根节点不包含字符,除根节点外每一个节点都只包含一个字符。

2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

3.每个节点的所有子节点包含的字符都不相同。

 

Trie中每个节点有一个特殊标记作为结束符号,通过该标记可以判断当前节点是否是一个字符串的终结节点。

下图是一个Trie树的例子,记录了to,tea,ted,ten,a,i,in,inn这些words(以蓝色结尾)。

《Trie树分析》

个人总结了如下方法:

Trie树的节点类

class Node{
     //节点字符值
     private char val;
     
     //节点的子节点,即以根节点到该节点的值组成的字符串为前缀的字符串构建的节点
     private Map<Character,Node> childrens=new HashMap<Character,Node>();
     
     //是否为结束节点,即一个字符串是否到达末尾节点   当end>0时表示结束节点
     private int end=0;
     //从根节点到该结束节点组成的字符串的重复数量  即单词列表中每个单词的词频
     private int dumpliNum=0;
     
     //以到达该节点组成的子串为前缀的字符串数量
     private int prexNum=0;


    /**
     * @return the dumpliNum
     */
    public int getDumpliNum() {
        return dumpliNum;
    }


    /**
     * @param dumpliNum the dumpliNum to set
     */
    public void setDumpliNum(int dumpliNum) {
        this.dumpliNum = dumpliNum;
    }


    /**
     * @return the prexNum
     */
    public int getPrexNum() {
        return prexNum;
    }


    /**
     * @param prexNum the prexNum to set
     */
    public void setPrexNum(int prexNum) {
        this.prexNum = prexNum;
    }


    /**
     * @return the val
     */
    public char getVal() {
        return val;
    }


    /**
     * @param val the val to set
     */
    public void setVal(char val) {
        this.val = val;
    }


    /**
     * @return the childrens
     */
    public Map<Character, Node> getChildrens() {
        return childrens;
    }


    /**
     * @param childrens the childrens to set
     */
    public void setChildrens(Map<Character, Node> childrens) {
        this.childrens = childrens;
    }


    /**
     * @return the end
     */
    public int getEnd() {
        return end;
    }


    /**
     * @param end the end to set
     */
    public void setEnd(int end) {
        this.end = end;
    }
 }

Trie树的初始化

public class Trie {

    //待构建的字符串列表
    private String words[];
    private Node root;

    public Trie(String[] words){
        this.root=new Node();
        this.words=words;
  }
}

Trie树的插入

//插入方法
   public void insert(String word){
        if(word==""||word==null){
            return;
        }
        Node cur=root;
        char [] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            //如果不存在该值,则创建新节点
            char val=chars[i];
            if(!cur.getChildrens().containsKey(val)){
                Node newNode=new Node();
                newNode.setVal(val);
                cur.getChildrens().put(val,newNode);
            }
            //如果已经存在则pass
            cur=cur.getChildrens().get(val);
            cur.setPrexNum(cur.getPrexNum()+1);
        }

        cur.setEnd(1);
        cur.setDumpliNum(cur.getDumpliNum()+1);
    }

Trie树的构建

public Node builtTrie(){
        for (int i=0;i<words.length;i++){
            insert(words[i]);
        }
        return root;
}

Trie树的查找

public boolean search(String word){
        boolean flag=true;
        Node cur=root;
        char[] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            if(!cur.getChildrens().containsKey(chars[i])){
               returnfalse;
            }
           cur=cur.getChildrens().get(chars[i]);//更新游标
         }
        return flag&&cur.getEnd()>0;//并且字符串最后一个字符必须是结束节点
}

 

Trie树中是否包某个前缀

    //以某个字符串开头  比如字符串列表中有[abb,abbb],则ab返回true
    public boolean startWith(String word){
        boolean flag=true;
        Node cur=root;
        char[] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            if(!cur.getChildrens().containsKey(chars[i])){
               return false;
            }
           cur=cur.getChildrens().get(chars[i]);//更新游标
         }
        //return flag&&cur.getEnd()>0;//并且该节点是结束节点
        return flag;//和查找相比去掉了字符串最后一个字符要是结束节点

}

Trie树前序遍历

  // 前序遍历,获取从指定节点开始,到所有结束节点组成的字符串
    Map<String, Node> preTraversal(Nodenode,String word){
        Map<String, Node> m=new HashMap<String,Node>();
        if(node!=null){
            if(node!=root&&node.getEnd()>0){
              m.put(word, node);
            }
            for(Map.Entry<Character, Node>entry:node.getChildrens().entrySet()){
                Node cur=entry.getValue();
                String temp=word+String.valueOf(cur.getVal());
               m.putAll(preTraversal(cur,temp));
             }

        }
        return m;
}

 

获取指定前缀出现次数

   //获取指定前缀出现次数
    int countPrefix(String prefix){
        Node cur =root;
        char[] chars=prefix.toCharArray();
        while(cur.getChildrens().size()>0){
            for(int i=0;i<chars.length;i++){
                if(cur.getChildrens().containsKey(chars[i])){
                   cur=cur.getChildrens().get(chars[i]);
                }
            }
            return cur.getPrexNum();
        }
        return 0;
}

 

获取含有指定前缀的所有单词

    //获取含有指定前缀的所有单词
    Map<String,Node>containPrefixOfwords(String prefix){
        Map<String,Node> m=newHashMap<String,Node>();
        //找到前缀的最后一个单词的所在节点
        Node cur=root;
        char[] chars=prefix.toCharArray();
        while(cur.getChildrens().size()>0){
            for(int i=0;i<chars.length;i++){
                if(cur.getChildrens().containsKey(chars[i])){
                   cur=cur.getChildrens().get(chars[i]);
                }else{
                    return null;
                }
            }
            break;
        }
        m.putAll(preTraversal(cur,prefix));
        return m;
}

全部代码和测试类

package trie;


import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**
 * 
 *Trie
 * Trie树
 *@author: 汤高
 *@date: :2018-1-8 下午4:33:44
 *@version 1.0
 */
public class Trie {
    //待构建的字符串列表
    private String words[];
    private Node root;
    
    public Trie(String[] words){
        this.root=new Node();
        this.words=words;
    }
    public Node builtTrie(){
        int index=0;
        for (int i=0;i<words.length;i++){
            insert(words[i],++index);
        }
        return root;
        
    }
    //插入方法
    public void insert(String word){
        if(word==""||word==null){
            return;
        }
        Node cur=root;
        char [] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            //如果不存在该值,则创建新节点
            char val=chars[i];
            if(!cur.getChildrens().containsKey(val)){
                Node newNode=new Node();
                newNode.setVal(val);
                cur.getChildrens().put(val, newNode);
            }
            //如果已经存在则pass
            cur=cur.getChildrens().get(val);
            cur.setPrexNum(cur.getPrexNum()+1);
        }
        cur.setEnd(1);
        cur.setDumpliNum(cur.getDumpliNum()+1);
    }
    
    //插入方法
    public void insert(String word,int index){
        if(word==""||word==null){
            return;
        }
        Node cur=root;
        char [] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            //如果不存在该值,则创建新节点
            char val=chars[i];
            if(!cur.getChildrens().containsKey(val)){
                Node newNode=new Node();
                newNode.setVal(val);
                cur.getChildrens().put(val, newNode);
            }
            //如果已经存在则pass
            cur=cur.getChildrens().get(val);
            cur.setPrexNum(cur.getPrexNum()+1);
        }
        cur.setEnd(index);
        cur.setDumpliNum(cur.getDumpliNum()+1);
        
    }
    
    //查找方法
    public boolean search(String word){
        boolean flag=true;
        Node cur=root;
        char[] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            if(!cur.getChildrens().containsKey(chars[i])){
               return false;
            }
            cur=cur.getChildrens().get(chars[i]);//更新游标
         }
       
        return flag&&cur.getEnd()>0;//并且字符串最后一个字符必须是结束节点
    }
    
    //以某个字符串开头  比如 字符串列表中有[abb,abbb],则ab返回true
    public boolean startWith(String word){
        boolean flag=true;
        Node cur=root;
        char[] chars=word.toCharArray();
        for(int i=0;i<chars.length;i++){
            if(!cur.getChildrens().containsKey(chars[i])){
               return false;
            }
            cur=cur.getChildrens().get(chars[i]);//更新游标
         }
        //return flag&&cur.getEnd()>0;//并且该节点是结束节点
        return flag;//和查找相比去掉了 字符串最后一个字符要是结束节点
    }
    
    //前序遍历
    Map<String, Node> preTraversal(Node node,String word){
        Map<String, Node> m=new HashMap<String, Node>();
        if(node!=null){
            if(node!=root&&node.getEnd()>0){
              m.put(word, node);
            }
            for(Map.Entry<Character, Node> entry:node.getChildrens().entrySet()){
                Node cur=entry.getValue();
                String temp=word+String.valueOf(cur.getVal());
                m.putAll(preTraversal(cur,temp));
             }
        }
        return m;
    }
    
    //获取指定前缀出现次数
    int countPrefix(String prefix){
        Node cur =root;
        char[] chars=prefix.toCharArray();
        while(cur.getChildrens().size()>0){
            for(int i=0;i<chars.length;i++){
                if(cur.getChildrens().containsKey(chars[i])){
                    cur=cur.getChildrens().get(chars[i]);
                }
            }
            return cur.getPrexNum();
        }
        return 0;
    }
    
    //获取含有指定前缀的所有单词
    Map<String,Node> containPrefixOfwords(String prefix){
        Map<String,Node> m=new HashMap<String,Node>();
        //找到前缀的最后一个单词的所在节点
        Node cur=root;
        char[] chars=prefix.toCharArray();
        while(cur.getChildrens().size()>0){
            for(int i=0;i<chars.length;i++){
                if(cur.getChildrens().containsKey(chars[i])){
                    cur=cur.getChildrens().get(chars[i]);
                }else{
                    return null;
                }
            }
            break;
        }
        m.putAll(preTraversal(cur,prefix));
        return m;
    }
    
    String getMax(){
        String result="";
        Stack<Node> stack=new Stack<Node>();
        stack.push(root);
        while(!stack.isEmpty()){
            Node node=stack.pop();
            if(node.getEnd()>0||node==root){
                if(node!=root){
                    String word= this.words[node.getEnd()-1];
                    if(word.length()>result.length()||
                            word.length()==result.length()&&result.compareTo(word)>0){//最长的且具有最小字典序的word
                       
                        result=word;
                    }
                }
                for(Node n:node.getChildrens().values()){//拿到子节点
                    stack.push(n);
                }
            }
        }
        return result;
        
    }
    
    class Node{
     //节点字符值
     private char val;
     
     //节点的子节点,即以根节点到该节点的值组成的字符串为前缀的字符串构建的节点
     private Map<Character,Node> childrens=new HashMap<Character,Node>();
     
     //是否为结束节点,即一个字符串是否到达末尾节点   当end>0时表示结束节点
     private int end=0;
     //从根节点到该结束节点组成的字符串的重复数量  即单词列表中每个单词的词频
     private int dumpliNum=0;
     
     //以到达该节点组成的子串为前缀的字符串数量
     private int prexNum=0;


    /**
     * @return the dumpliNum
     */
    public int getDumpliNum() {
        return dumpliNum;
    }


    /**
     * @param dumpliNum the dumpliNum to set
     */
    public void setDumpliNum(int dumpliNum) {
        this.dumpliNum = dumpliNum;
    }


    /**
     * @return the prexNum
     */
    public int getPrexNum() {
        return prexNum;
    }


    /**
     * @param prexNum the prexNum to set
     */
    public void setPrexNum(int prexNum) {
        this.prexNum = prexNum;
    }


    /**
     * @return the val
     */
    public char getVal() {
        return val;
    }


    /**
     * @param val the val to set
     */
    public void setVal(char val) {
        this.val = val;
    }


    /**
     * @return the childrens
     */
    public Map<Character, Node> getChildrens() {
        return childrens;
    }


    /**
     * @param childrens the childrens to set
     */
    public void setChildrens(Map<Character, Node> childrens) {
        this.childrens = childrens;
    }


    /**
     * @return the end
     */
    public int getEnd() {
        return end;
    }


    /**
     * @param end the end to set
     */
    public void setEnd(int end) {
        this.end = end;
    }
 }
    
    public static void main(String[] args) {
       String[] words=new String[]{"ab","abb","ab","abbc","b","ba","bdd"};
      
        Trie t=new Trie(words);
        Node root=t.builtTrie();//构建trie树
            
        System.out.println("trie树包含bdd吗?  "+t.search("bdd"));
        System.out.println("trie树包含bd吗?  "+t.search("bd"));


        System.out.println("trie树包含bd前缀吗?"+t.startWith("bd"));
        System.out.println("trie树包含bdX前缀吗?"+t.startWith("bdX"));
        
        System.out.println("根节点前序遍历,获取所有单词和它出现的次数");
        for(Map.Entry<String, Node> en:t.preTraversal(root, "").entrySet()){
            System.out.println(en.getKey()+"出现几次:"+en.getValue().dumpliNum);
        }
        
        System.out.println("以ab开头的前缀出现几次:"+t.countPrefix("ab"));
        
        System.out.println("包含ab为前缀的所有单词");
        for(Map.Entry<String, Node> en:t.containPrefixOfwords("ab").entrySet()){
            System.out.println(en.getKey()+"单词出现几次:"+en.getValue().dumpliNum);
        }
    }
}

测试结果

trie树包含bdd吗?  true

trie树包含bd吗?  false

trie树包含bd前缀吗?true

trie树包含bdX前缀吗?false

根节点前序遍历,获取所有单词和它出现的次数

abb出现几次:1

b出现几次:1

ba出现几次:1

bdd出现几次:1

abbc出现几次:1

ab出现几次:2

ab开头的前缀出现几次:4

包含ab为前缀的所有单词

abb单词出现几次:1

ab单词出现几次:2

abbc单词出现几次:1

转载请指明http://blog.csdn.net/tanggao1314/article/details/79004795

    原文作者:Trie树
    原文地址: https://blog.csdn.net/tanggao1314/article/details/79004795
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞