Trie树——在一个字符串集合中快速查找某个字符串

Trie树又叫“字典树”,是一种专门处理字符串匹配的数据结构,用来解决在一个字符串集合中快速查找某个字符串的问题。
本质:利用字符串之间的公共前缀,将重复的前缀合并在一起。

用6个字符串hello,how,hi,her,so,see来组织成Trie树的结构:
Trie树——在一个字符串集合中快速查找某个字符串_第1张图片其中,根节点不包含任何信息。每个节点表示一个字符串中的字符,从根节点到红色节点表示一个字符串。

Trie树的两个操作:
1.将字符串集合构造成Trie树
2.在Trie树中查询一个字符串

构造Tire树:Trie树是一个多叉树,可以用下面这个数据结构来存储一个节点的所有子节点的指针:

class TrieNode {
  char data;
  TrieNode children[26];
}

以字符集只包含a-z为例,通过下标与字符一一映射的数组,来存储子节点的指针,如下图所示:
Trie树——在一个字符串集合中快速查找某个字符串_第2张图片

在Trie树中查找一个字符串:通过字符的ASCII码减去’a’的ASCII码,迅速找到匹配的子节点的指针。

相关代码:

public class Trie {
  private TrieNode root = new TrieNode('/'); // 存储无意义字符

  // 往 Trie 树中插入一个字符串
  public void insert(char[] text) {
    TrieNode p = root;
    for (int i = 0; i < text.length; ++i) {
      int index = text[i] - 'a';
      if (p.children[index] == null) {
        TrieNode newNode = new TrieNode(text[i]);
        p.children[index] = newNode;
      }
      p = p.children[index];
    }
    p.isEndingChar = true;
  }

  // 在 Trie 树中查找一个字符串
  public boolean find(char[] pattern) {
    TrieNode p = root;
    for (int i = 0; i < pattern.length; ++i) {
      int index = pattern[i] - 'a';
      if (p.children[index] == null) {
        return false; // 不存在 pattern
      }
      p = p.children[index];
    }
    if (p.isEndingChar == false) return false; // 不能完全匹配,只是前缀
    else return true; // 找到 pattern
  }

  public class TrieNode {
    public char data;
    public TrieNode[] children = new TrieNode[26];
    public boolean isEndingChar = false;
    public TrieNode(char data) {
      this.data = data;
    }
  }
}

时间复杂度分析:构建Trie树的过程,需要扫描所有的字符串,时间复杂度为O(n),其中n为所有字符串的长度和。一旦Tire树构建成功之后,后续的查询操作会非常高效,如果要查找的字符串的长度为k,则查询操作的时间复杂度为O(k)。

缺点:
Tire树用这种数据结构的节点,非常消耗内存。在一组字符串中查找一个字符串,其实Trie的表现并不好。
第一,字符串中包含的字符集不能太大,不然存储空间会浪费很多,即使可以优化,但也要付出牺牲查询、插入的代价。
第二,要求字符串集合中的字符串前缀重合比较多。
第三,指针串起来的数据块是不连续的,Trie树用到了指针,对缓存非常不友好。
第四,从0开始实现一棵Trie树,还要保证没有bug,不如直接利用现成类库中提供的红黑树或者散列表来实现。

以上,说明Trie树不适合用来精确匹配查找,但可以用来查找前缀匹配的字符串,例如利用Trie树来实现搜索关键词的的提示功能。

你可能感兴趣的:(算法基础)