数据结构——Trie

单词查找树、后缀树Trie

参考资料:
leetcode之实现Trie
北大数据结构与算法课程:Trie
具体的数据结构代码java实现
算法第四版

基本定义

(trie发音为try),这个数据结构作用是取出数据,基本性质包括查找和插入算法,主要应用于数据检索以及大规模的英文数据字典(中文需要使用双数组Trie)

数据结构——Trie_第1张图片

  1. 每个节点只能有一个指向它的节点,称为父结点
  2. 每个键所关联的值保存在该键最后一个字母所在结点中

leetcode下具体的实现代码,实现实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

这里算是简化了,node结点中并没有存储value值,关键判断通过数组索引存储26个英文字符的ASSIC码来进行判断

class Trie {
     

    /**
     * Initialize your data structure here.
     */
    private TrieNode head;

    public Trie() {
     
        head = new TrieNode();

    }

    /**
     * Inserts a word into the trie.
     */
    public void insert(String word) {
     
        TrieNode root = head;
        for (int i = 0; i < word.length(); i++) {
     
            if (!root.containsKey(word.charAt(i))) {
     
                root.put(word.charAt(i), new TrieNode());
            }
            root = root.get(word.charAt(i));
        }
        root.setEnd();
    }

    /**
     * Returns if the word is in the trie.
     */
    public boolean search(String word) {
     
        TrieNode root = head;
        for (int i = 0; i < word.length(); i++) {
     
            if(root.get(word.charAt(i))==null){
     
                return false;
            }else{
     
                root = root.get(word.charAt(i));
            }
        }
        return root.isEnd();
    }

    /**
     * Returns if there is any word in the trie that starts with the given prefix.
     */
    public boolean startsWith(String prefix) {
     
        TrieNode root = head;
        for (int i = 0; i < prefix.length(); i++) {
     
            if(root.get(prefix.charAt(i))==null){
     
                return false;
            }else{
     
                root = root.get(prefix.charAt(i));
            }
        }
        return true;
    }

    class TrieNode {
     

        // R links to node children
        private TrieNode[] links;

        private final int R = 26;

        private boolean isEnd;

        public TrieNode() {
     
            links = new TrieNode[R];
        }

        public boolean containsKey(char ch) {
     
            return links[ch - 'a'] != null;
        }

        public TrieNode get(char ch) {
     
            return links[ch - 'a'];
        }

        public void put(char ch, TrieNode node) {
     
            links[ch - 'a'] = node;
        }

        public void setEnd() {
     
            isEnd = true;
        }

        public boolean isEnd() {
     
            return isEnd;
        }
    }

}

查找操作

查找可能存在三种情况:

  1. 查找到字符串最后一个字符时,对应的结点不为空,且为叶子结点。此时可以证明恰好匹配到该字符串
  2. 查找到最后一个字符时此结点不为空,但不为叶子结点,则说明当前查找字符串是某一字符串但前缀
  3. 查找到一个空结点美则说明未命中

遍历操作 (查找所有字符串,查找所有带有某个前缀的字符串)

算法思想:回溯法
利用递归以及队列来完成

数据结构——Trie_第2张图片

利用这个递归算法,还可以解决通配符匹配以及最长前缀的问题,最长前缀也是同样通过递归,增加一个变量length来记录当前长度,当遇到null或者字符串结束时候返回

private int search(Node x,String s, int d, int length){
     
if(x==null) return;
if(x.val !=null) length = d;
if(d == s.length()) return length;
char c = s.charAt(d);
return search(x.next[c],s,d+1,length)
}

两个问题

1、Trie 树为什么是一种比较高效的索引结构?
我自己的理解是trie树是一种以时间换空间的数据结构,通过额外的字符集的存储空间来换取常数级别的查找时间,可以达到O(n)的查找时间,在最坏情况下我们只需要检查完查找的字符键有含有的结点即可完成查找或者插入操作,即查找7位车牌号时,我们只需要检查最多8个结点。
2、Trie 树适合于中文吗?
不适合。Trie树的插入和查找都与字符串的长度成线性关系。代码实现容易,维护容易。
但是Trie树的一个特点是它的空间复杂度与字符集大小有关。因为英文的字符集只有26个字母,而中文字符集非常大。
我们可以看一个空间需求的表:
数据结构——Trie_第3张图片
在最差的情况下即字母表大小为R,N个随机键构造的单词查找树中,所需要检查的节点数为logRN。
而链接的总数在RN与RNw之前,w是键的平均长度
我们也可以把保存了R个单词的查找树称为R向单词查找树。
因此存储中文会花费很大空间代价
解决方法是对传统的字典树进行改进。拼音表示和哈希表示,获取采用双数组 Trie的形式

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