字典树(Trie) 单词查找树

字典树(Trie) 单词查找树

字典树不言而喻就是类似字典的树形数据结构。假如要查找单词dog,那我们肯定是从字典的d部分查起;单词dog和doge自然也是相去不远,因为他们有公共的前缀dog;字典的编排也是按照字典序的…

参考学习《算法竞赛入门到进阶》 -罗勇军、郭卫斌

字典树的常见应用

  1. 字符串检索、查询
  2. 词频统计
  3. 字符串排序
  4. 前缀匹配(搜索引擎、Linux指令自动补全)

字典树的实现

1. 正规字典树

定义字典树数据结构,用指针指向下一层子树,但是对空间要求较高,示意图如图:
在这里插入图片描述

此为字典树节点属性:

//字典树节点
public static class TrieNode{
        public TrieNode[] tn;   //下一层子树
        public int num;			//以当前字符串为前缀的单词数量
        public TrieNode(){		//构造方法
            tn = new TrieNode[26];
        }
}

字典树的根节点,不存数据

//字典树根节点
public static TrieNode root = new TrieNode();

插入指定字符串s到字典树,逐个将s中的字母放到对的位置 上

//插入方法
public static void insert(String s){
        TrieNode temp = root;   //获取根节点
    	//遍历s
        for(int i = 0;i < s.length();i++){
            if(temp.tn[s.charAt(i)-'a'] == null)  
                temp.tn[s.charAt(i)-'a'] = new TrieNode();
            temp = temp.tn[s.charAt(i)-'a'];
            temp.num++;
        }
}

检索单词s或前缀s出现次数

//检索
//与遍历类似
public static int find(String s){
    TrieNode temp = root;   
    for(int i = 0;i < s.length();i++){
        if(temp.tn[s.charAt(i)-'a'] == null)
            return 0;
        temp = temp.tn[s.charAt(i)-'a'];
    }
    return temp.num;
}

字典序输出字典树

public static void sort(TrieNode root,String s){
        //判断是否到达最后一个字母
        if(sortCheck(root)){  
            System.out.println(s);
            return;
        }

        //通过前序遍历实现排序
        for(int i = 0;i < 26;i++){
            if(root.tn[i] != null){
                String temp = s;
                temp += String.valueOf((char)('a'+i));
                sort(root.tn[i],temp);
            }
        }
}
public static boolean sortCheck(TrieNode root){
        for(int i = 0;i < 26;i++)
            if(root.tn[i] != null)
                return false;
        return true;
}

完整源代码如下

public class Trie {
    public static TrieNode root = new TrieNode();

    public static class TrieNode{
        public TrieNode[] tn;
        public int num;
        public TrieNode(){
            tn = new TrieNode[26];
        }
    }

    public static void insert(String s){
        TrieNode temp = root;
        for(int i = 0;i < s.length();i++){
            if(temp.tn[s.charAt(i)-'a'] == null)
                temp.tn[s.charAt(i)-'a'] = new TrieNode();
            temp = temp.tn[s.charAt(i)-'a'];
            temp.num++;
        }
    }
    public static int find(String s){
        TrieNode temp = root;
        for(int i = 0;i < s.length();i++){
            if(temp.tn[s.charAt(i)-'a'] == null)
                return 0;
            temp = temp.tn[s.charAt(i)-'a'];
        }
        return temp.num;
    }

    public static boolean sortCheck(TrieNode root){
        for(int i = 0;i < 26;i++)
            if(root.tn[i] != null)
                return false;
        return true;
    }

    public static void sort(TrieNode root,String s){
        //判断是否到达最后一个字母
        if(sortCheck(root)){  
            System.out.println(s);
            return;
        }

        //通过前序遍历实现排序
        for(int i = 0;i < 26;i++){
            if(root.tn[i] != null){
                String temp = s;
                temp += String.valueOf((char)('a'+i));
                sort(root.tn[i],temp);
            }
        }
    }
    public static void main(String[] args) {
        insert("gerge");
        insert("eroni");
        insert("cwrf");
        insert("oqpwinfw");
        insert("wnkca");
        insert("axcnasixu");
        insert("abf");
        insert("abcesf");
        sort(root,"");
    }
}

2. 数组实现字典树

竞赛中常用,是一种更为紧凑的存储方式。

基本属性

//用数组定义字典树,存储下一个字符的位置(注意是位置)
public static int[][] trie = new int[100000][26];
//以某一字符串为前缀的单词的数量
public static int[] num = new int[100000];
//当前新分配的存储位置
public static int pos=1;

数组实现与第一种实现大致相同,主要是注意理解pos是新分配的存储位置(行数),笔者建议代入几条数据模拟一下,完整源代码如下

public class TrieArray {
    public static int[][] trie = new int[100000][26];
    public static int[] num = new int[100000];
    public static int pos=1;
    public static void main(String[] args) {
        insert("gerge");
        insert("eroni");
        insert("cwrf");
        insert("oqpwinfw");
        insert("wnkca");
        insert("axcnasixu");
        insert("abf");
        insert("abcesf");
        sort(0,"");
    }
    public static boolean sortCheck(int line){
        for(int i = 0;i < 26;i++)
            if(trie[line][i] != 0)
                return false;
        return true;
    }
    public static void sort(int line,String s){
        if(sortCheck(line)){
            System.out.println(s);
            return;
        }
        for(int i = 0;i < 26;i++){
            String temp = s;
            if(trie[line][i] != 0){
                temp += String.valueOf((char)('a'+i));
                sort(trie[line][i],temp);
            }
        }
    }
    public static void insert(String s){
        int p = 0;
        for(int i = 0;i < s.length();i++){
            int n = s.charAt(i) - 'a';
            if(trie[p][n] == 0)
                trie[p][n] = pos++;
            p = trie[p][n];
            num[p]++;
        }
    }
    public static int find(String s){
        int p = 0;
        for(int i = 0;i < s.length();i++){
            int n = s.charAt(i) - 'a';
            if(trie[p][n] == 0)
                return 0;
            p = trie[p][n];
        }
        return num[p];
    }
}

复杂度

  1. 时间复杂度:插入和查找单词的复杂度都是O(m),m是待插入/查询字符串的长度
  2. 空间复杂度:有公共前缀的单词只需要存一次公共前缀,节省了空间
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值