旧笔记整理:字典树

文章目录

  • 1.概念:
  • 2.字典树的java实现:
  • 3.字典树的应用:


1.概念:

字典树又叫查找树,是一种树形结构,是哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。他的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查找效率比哈希树高。

基本性质:

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符都不相同。

2.字典树的java实现:

package TrieTree;

import java.util.HashMap;

public class TrieTree {
	
	class Node{
		private int dump_count;
		private int prefix_count;
		private Node[] node;
		private boolean isleaf;
		
		public Node() {
			dump_count = 0;
			prefix_count = 0;
			node = new Node[26];
			isleaf = false;
		}
	}
	
	private Node root;
	//树根
	public TrieTree() {
		root = new Node();
	}
	
	public boolean isExist(String word){
        return search(this.root, word);
    }
	
	public boolean search(Node root,String word) {
		char[] ch = word.toCharArray();
		for(int i=0;i<ch.length;i++) {
			int index = ch[i] - 'a';
			if(root.node[index]==null) {
				return false;
			}
			root = root.node[index];
		}
		return true;
	}
	
	public void insert(String words){
        insert(this.root, words);
    }
	
	public void insert(Node root,String words) {
		words=words.toLowerCase();
		char[] ch = words.toCharArray();
		for(int i=0;i<ch.length;i++) {
			int index = ch[i] - 'a';
			if(root.node[index]==null) {
				root.node[index] = new Node();
				
			}
			root.node[index].prefix_count++;
			
			if(i+1==ch.length) {
				root.node[index].isleaf=true;
				root.node[index].dump_count++;
			}
			root = root.node[index];
		}
	}
	
	//遍历所有的元素
	public HashMap<String,Integer> getAllWords(){
		return preTraversal(this.root, "");
	}

	//遍历出当前前缀的元素的数量
	private HashMap<String, Integer> preTraversal(Node root, String prefixs) {
		//先弄一个hashmap村结果
		//然后定义边界条件
		//然后是递归过程
		//最后是返回值
		HashMap<String,Integer> hashMap = new HashMap<>();
		if(root!=null) {
			if(root.isleaf==true) {
				hashMap.put(prefixs, root.dump_count);
			}
			
			for(int i=0;i<root.node.length;i++) {
				if(root.node[i]!=null){
					char ch=(char) (i+'a');
					String tempStr=prefixs+ch;
					hashMap.putAll(preTraversal(root.node[i], tempStr));
				}
				
			}
		}
        return hashMap;
	}
	
	public HashMap<String, Integer> getWordsForPrefix(String prefix){
        return getWordsForPrefix(this.root, prefix);
    }
	
	
	//得到特定前缀所包含的字符集
	//这个方法说白了就是定位一个root,然后交给preTraversal
	private HashMap<String, Integer> getWordsForPrefix(Node root,String prefix){
		char[] ch = prefix.toCharArray();
		for(int i=0;i<prefix.length();i++) {
			int index = ch[i] - 'a';
			if(root.node[index]==null) {
				return null;
			}
			root = root.node[index];
		}
		return preTraversal(root,prefix);
	}
	
	public static void main(String args[]){
		TrieTree trie = new TrieTree();
        trie.insert("I");
        trie.insert("Love");
        trie.insert("China");
        trie.insert("China");
        trie.insert("China");
        trie.insert("China");
        trie.insert("China");
        trie.insert("xiaoliang");
        trie.insert("xiaoliang");
        trie.insert("man");
        trie.insert("handsome");
        trie.insert("love");
        trie.insert("chinaha");
        trie.insert("her");
        trie.insert("know");
 
        HashMap<String,Integer> map=trie.getAllWords();
 
        for(String key:map.keySet()){
            System.out.println(key+" 出现: "+ map.get(key)+"次");
        }
 
 
        map=trie.getWordsForPrefix("chin");
 
        System.out.println("\n\n包含chin(包括本身)前缀的单词及出现次数:");
        for(String key:map.keySet()){
            System.out.println(key+" 出现: "+ map.get(key)+"次");
        }
 
        if(trie.isExist("xiaoming")==false){
            System.out.println("\n\n字典树中不存在:xiaoming ");
        }
 
 
    }

}

3.字典树的应用:

  • 最大限度的减少无谓的字符串比较,查询效率比哈希表高
  • 字典树的核心思想是用空间换时间。既利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的

缺点:Trie树的内存消耗非常大

字典树查找复杂度分析:
在trie树中查找一个关键字的时间和树中包含的结点数无关,而取决于组成关键字的字符数。而二叉查找树的查找时间和树中的结点数有关O(log2n)。
如果要查找的关键字可以分解成字符序列且不是很长,利用trie树查找速度优于二叉查找树。如:
若关键字长度最大是5,则利用trie树,利用5次比较可以从26^5=11881376个可能的关键字中检索出指定的关键字。而利用二叉查找树至少要进行23.5次比较。

下面来看一个问题:
1)有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的10个词。

下面我们来分析:
1:1G的文件,对这么大的文件做什么操作都是不容易的,所以我们不妨把它分解为小文件:

分而治之/hash映射:顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200k左右。如果其中的有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M。

2:hash_map统计:对每个小文件,采用trie树/hash_map等统计每个文件中出现的词以及相应的频率。

3:堆/归并排序:取出出现频率最大的100个词(可以用含100个结点的最小堆)后,再把100个词及相应的频率存入文件,这样又得到了5000个文件。最后就是把这5000个文件进行归并(类似于归并排序)的过程了。

这个问题能涉及到的知识:文件操作,分治思想,hashmap和字典树统计,堆排序和归并排序。

接下来我们更加深入的了解一下:为什么字典树的查找效率比哈希表快?

参考文章:
https://blog.csdn.net/stevenkylelee/article/details/38343985

你可能感兴趣的:(树,数据结构)