一、知识简介
最近在看字符串算法了,其中字典树、AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用。
字典树(Trie)可以保存一些字符串->值的对应关系。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 映射,只不过 Trie 的 key 只能是字符串。
Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(k) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。
至于Trie树的实现,可以用数组,也可以用指针动态分配,我做题时为了方便就用了数组,静态分配空间。
Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
Trie树的基本性质可以归纳为:
(1)根节点不包含字符,除根节点意外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3)每个节点的所有子节点包含的字符串不相同。
Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。
基本思想(以字母树为例):
1、插入过程
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点标记为红色,表示该单词已插入Trie树。
2、查询过程
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未标记为红色,则表示该单词不存在,若最后的节点标记为红色,表示该单词存在。
二、字典树的数据结构:
利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询操作的复杂度。
则可声明包含Trie树的结点信息的结构体:
/* 树形结构的使用.*/
private class Node<T>{
/* 节点集合. */
private Map<T,Node> nodeMap = new HashMap<T, Node>();
/* 最长公共前缀数量. */
private int num = 0;
/* 是否是叶子节点. */
private boolean isLeaf = false;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isLeaf() {
return isLeaf;
}
public void setLeaf(boolean leaf) {
isLeaf = leaf;
}
public void put(T key,Node node){
this.nodeMap.put(key,node);
}
public Node get(T key){
return this.nodeMap.get(key);
}
public boolean containsKey(T key){
return this.nodeMap.containsKey(key);
}
}
二、trie树的图片
三、撸一段自己的代码
package com.liubin.study.hiho;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* @Description:
* 题目链接:
* http://hihocoder.com/contest/hiho2/problem/1
* Trie树
* 解答思路:
*
* @ClassName: Hiho2
* @Package: com.liubin.study.hiho
* @Author: liubin@hsyuntai.com
* @Date: 2017/3/20 15:23
* @Copyright: 版权归 HSYUNTAI 所有
* <ModifyLog>
* @ModifyContent:
* @Author:
* @Date: </ModifyLog>
*/
public class Hiho2 {
private Node<Character> root = new Node<Character>();
/**
* 构建字典树
* @param str
*/
public void add(String str){
Node<Character> index = this.root;
for(int i = 0; i < str.length(); i++){
Node<Character> node = null;
//判断是否存在该节点
if(!index.containsKey(str.charAt(i))){
node = new Node<Character>();
}else{
node = index.get(str.charAt(i));
}
//处理节点的数据
node.setNum(node.getNum()+1);
if(i == str.length() - 1){
node.setLeaf(true);
}
index.put(str.charAt(i),node);
index = node;
}
}
/**
* 获取某个指定的字符串,是多少个字符串的最长公共前缀
* @param str
* @return
*/
public Integer get(String str){
Node<Character> index = this.root;
for(int i = 0; i < str.length(); i++){
//判断是否存在该节点
if(!index.containsKey(str.charAt(i))){
return 0;
}else{
index = index.get(str.charAt(i));
}
}
return index.getNum();
}
/* 树形结构的使用.*/
private class Node<T>{
/* 节点集合. */
private Map<T,Node> nodeMap = new HashMap<T, Node>();
/* 最长公共前缀数量. */
private int num = 0;
/* 是否是叶子节点. */
private boolean isLeaf = false;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isLeaf() {
return isLeaf;
}
public void setLeaf(boolean leaf) {
isLeaf = leaf;
}
public void put(T key,Node node){
this.nodeMap.put(key,node);
}
public Node get(T key){
return this.nodeMap.get(key);
}
public boolean containsKey(T key){
return this.nodeMap.containsKey(key);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int num = in.nextInt();
Hiho2 hiho2 = new Hiho2();
for(int i = 0 ; i < num ; i++){
String str = in.next();
hiho2.add(str);
}
num = in.nextInt();
for(int i = 0; i < num; i++){
String str = in.next();
System.out.println(hiho2.get(str));
}
}
}