[翻译]数据结构——trie树介绍

1、开篇说明

1.本文原文来自于leetcode上的算法题Implement Trie的解决方案.
2.原文地址
3.新手献丑了,但愿你们轻拍~(微笑)java


2、原文在这

1.问题描述

算法题:
经过编写插入、查询、判断开头等方法完成一个trie树。node

例子:算法

Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // returns true
trie.search("app");     // returns false
trie.startsWith("app"); // returns true
trie.insert("app");   
复制代码

提示:数组

  • 你能够假设全部的输入都是由小写字母a-z组成的。
  • 全部的输入string数组都不为空。

总结:
这篇文章是写给中等水平的读者的,将会介绍数据结构trie(前缀树)和其中的常见操做。数据结构

2.解决方法:

2.1应用:
trie(前缀树)是一种树形数据结构,经常用来在字符串的数据集中检索一个关键词。目前,trie数据结构已经被高效地应用在了不少领域:
(1)自动填充
app

谷歌实时的关键词推荐
图片1.谷歌实时的关键词推荐

(2)拼写检查
图片2.在文字处理机中的拼写检查

(3)IP路由(最长的路由匹配)
图片3.最长路由匹配算法
(4)九键输入法的预测文字
图片4.九键输入法在1990年代应用在手机中用来输入文字
(5)完成文字游戏
图片5.经过减小搜索空间,trie可以很好的完成boggle这个游戏

有不少其余的数据结构如,平衡树,hash表都可以在一个string的数据集中查找单词,可是咱们为何要使用trie呢?虽然hash表对于找到一个关键词(key)只须要 O (1)的时间复杂度,可是在下列操做中,它就表现得不是很高效了。

  • 找到拥有共同前缀的全部关键词(key)。
  • 根据字典序枚举全部字符串

trie优于hash表的另一个缘由是,但hash表的数据规模扩大后,将会出现不少的hash碰撞,所以查询时间复杂度将会提升到O (n),n 是插入的关键词的个数。而相比于hash表,trie在存储不少具备共同前缀的关键词时须要的空间更少。在这个例子里trie只须要O (m)的时间复杂度(m 是关键词的长度)。而在平衡树里查询一个关键词,则须要花费O (m logn)
2.2 trie节点结构
trie是一个有根树,它的节点有如下几个字段:spa

  • 与子节点间最多有R个链接,每一个链接对应到R个字母中的一个。这个R个字母来自于字母表。在这篇文章中,咱们假设R是26,即26个小写的拉丁字母。
  • 一个布尔值isEnd,说明该布尔值说明当前节点是不是一个关键词的结尾,不然就只是该关键词的前缀。
    图6.表明关键字“leet”在trie中的表达

    java编写的trie节点
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;
    }
}
复制代码

2.3 trie中最多见的操做——添加和查询关键词
(1)添加关键词到trie中
咱们经过遍历trie来插入关键词。咱们从根节点开始,搜寻和关键词第一个字母对应的链接,这里通常有两种状况:设计

  • 若是链接存在,那么咱们就顺着这个链接往下移到下一子层,接着搜寻关键词的下一个字母对应的链接。
  • 若是链接不存在,那么咱们就新建一个trie节点,对应着如今的关键词字母,创建与父节点的链接。

咱们重复这个步骤,直处处理完关键词的最后一个字母,而后标记最后的节点为结束节点。算法结束。 code

插入关键字到trie中

java编写的插入方法

class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

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

复杂度分析:cdn

  • 时间复杂度O(m),m 是关键词的长度。在算法的每一次循环中,咱们要么检查节点要么新建一个节点,直到该关键词的最后一个字母。因此,这只须要进行m次操做。
  • 空间复杂度O(m)。最糟糕的状况是,新插入的关键词和已经存在trie的关键词没有共同的前缀,所以咱们必须插入m个新的节点,所以须要O(m)空间复杂度。

(2)在trie中搜索关键词
每个关键词在trie中均可以被一条从根节点到子节点的路径所表示。咱们将根据关键词的第一个字母从根节点开始搜索,而后检查节点上的每个链接对应的字母,通常有两种状况:

  • 存在对应关键词字母的链接,咱们将从该链接移动到下一个节点,而后搜索关键词的下一个字母对应的链接。
  • 对应的链接不存在,若是此时已经遍历到了关键词的最后一个字母,则把当前的节点标记为结束节点,而后返回true。固然还有另外两种状况,咱们会返回false:
    • 关键词的字母没有遍历完,但没有办法接着在trie中找到根据关键词字母的造成的路径,因此trie中不存在该关键词。
    • 关键词的字母的已经遍历完了,但当前的节点不是结束节点,所以搜索的关键词只是trie中的某一个关键字的前缀。

图8.在trie中搜索某个关键词
java编写搜索关键词的方法

class Trie {
    ...

    // search a prefix or whole key in trie and
    // returns the node where search ends
    private TrieNode searchPrefix(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
           char curLetter = word.charAt(i);
           if (node.containsKey(curLetter)) {
               node = node.get(curLetter);
           } else {
               return null;
           }
        }
        return node;
    }

    // Returns if the word is in the trie.
    public boolean search(String word) {
       TrieNode node = searchPrefix(word);
       return node != null && node.isEnd();
    }
}
复制代码

复杂度分析:

  • 时间复杂度:O(m)。在算法的每一步都是搜索关键词的下一个字母,所以在最差的状况下,算法须要执行m步。
  • 空间复杂度:O(1)。

(3)在trie中搜索关键词的前缀

这个方法和咱们在trie中用来搜索关键词的方法很相似。咱们从根节点开始移动,直到关键词前缀的每一个字母都被搜索到了,或者,没有办法在trie中根据关键词的当前字母找到接下去的路径。这个方法和前面提到的搜索关键词的惟一不一样在于,当咱们遍历到关键词前缀的最后一个字母时,咱们老是返回true,咱们不须要考虑当前的节点是否有结束标志,由于咱们只是搜索关键词的前缀,而不是整个关键词。

图9.在trie中搜索关键词前缀

java编写的搜索关键词前缀的方法

class Trie {
    ...

    // Returns if there is any word in the trie
    // that starts with the given prefix.
    public boolean startsWith(String prefix) {
        TrieNode node = searchPrefix(prefix);
        return node != null;
    }
}
复制代码

复杂度分析:

  • 时间复杂度:O(m)
  • 空间复杂度:O(1)

3. 应用问题
这里有一些很是合适应你们去练习的问题,这些问题都能用trie数据结构解决。

这篇分析出自@elmirap

相关文章
相关标签/搜索