Go实现字典树Trie

581 阅读2分钟
字典树(Trie)

又称前缀树,其每个节点包含以下字段:

  • 指向子节点的指针数组children
  • 布尔字段isEnd,表示该节点是否是字符串的结尾

字典树常用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。 image.png 我们首先要明确的一点是字符串并不是存储在一个节点中的,而是存储在一条链路上

数组实现
type Trie struct {
    children [26]*Trie
    isEnd    bool
}
//初始化一个字典树
func Constructor() Trie {
    return Trie{}
}
//insert:从根节点开始按输入的word的字符依次向下延伸,若无代表次字符的结点,创建结点
func (t *Trie) Insert(word string) {
    root := t
    for _, ch := range word {
        ch -= 'a'
        if root.children[ch] == nil {
            root.children[ch] = &Trie{}
        }
        root = root.children[ch]
    }
    root.isEnd = true
}

//查找字串是否在前缀树上,找到则返回要查找的字串,否则返回null
func (t *Trie) SearchPrefix(prefix string) *Trie {
    root := t
    for _, ch := range prefix {
        ch -= 'a'
        if root.children[ch] == nil {
            return nil
        }
        root = root.children[ch]
    }
    return root
}

//向下寻找单词word,若找到,则末尾字符处判断是否为单词。
//因此,Trie类还需维护一个isEnd属性,用以标记到该节点为止的前缀是否为单词
func (t *Trie) Search(word string) bool {
    root := t.SearchPrefix(word)
    return root != nil && root.isEnd
}

//向下寻找前缀prefix。某一字符找不到则立即返回false,否则返回true
func (t *Trie) StartsWith(prefix string) bool {
    return t.SearchPrefix(prefix) != nil
}
哈希表实现

除去使用数组的方式,还可以使用哈希表的方式建立字典树

type Trie struct {
   children map[string]*Trie
   isEnd    bool
}

func Constructor() Trie {
   return Trie{
      children: make(map[string]*Trie),
      isEnd:    false,
   }
}

func (this *Trie) Insert(word string) {
   root := this
   for _, ch := range word {
      if root.children[string(ch)] == nil {
         root.children[string(ch)] = &Trie{children: make(map[string]*Trie)}
      }
      root = root.children[string(ch)]
   }
   root.isEnd = true
}

func (this *Trie) Search(word string) bool {
   root := this.SearchPrefix(word)
   return root != nil && root.isEnd 
}

func (this *Trie) StartsWith(prefix string) bool {
   return this.SearchPrefix(prefix) != nil
}

func (this *Trie) SearchPrefix(prefix string) *Trie {
   root := this
   for _, ch := range prefix {
      if root.children[string(ch)] == nil {
         return nil
      }
      root = root.children[string(ch)]
   }
   return root
}

查找功能和检索前缀都要遍历字符串,因此可以写一个公共方法searchPrefix,统一查找输进来的字符串,若能找到就返回,找不到就返回nil,然后再进行查找和检索前缀的需求。

补充一个Delete方法

刷题的时候(力扣208题)题目中是没有要求delete方法的,但是我们可能会有这类需求

func (this *Trie) Delete(word string) {
   node := this.SearchPrefix(word)
   //字典树中并没有word这个单词,直接返回
   if node == nil {
      return
   }
   //如果子树不为空,意味着链路往下还有其他单词存在,就将字串的结尾标识改为false
   if node.children != nil {
      node.isEnd = false
   } else { //如果子树为空,我们就向上查找上一个字串结尾标识,将之后部分删除,若该链路没有结尾标识,就将整条链路删除
      for i := len(word); i > 0; i-- {
         result := this.Search(word[:i])
         if result {
            this.children[string(word[i-1])] = nil
            return
         }
      }
      this.children[string(word[0])] = nil 
   }
   return
}