字典树(Trie)
又称前缀树,其每个节点包含以下字段:
- 指向子节点的指针数组children
- 布尔字段isEnd,表示该节点是否是字符串的结尾
字典树常用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
我们首先要明确的一点是字符串并不是存储在一个节点中的,而是存储在一条链路上
数组实现
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
}