Go语言数据结构与算法-Trie树
摘要:
Trie树 概述 Trie树,又叫字典树、前缀树(Prefix Tree)、单词查找树或键树,是一种很常用的树结构【多叉树】。 它被广泛用于各个方面,比如字符串检索、中文分词、求字符串最长公共前缀和字典排序等等。
核心思想
空间换时间:数据结构本身比较消耗空间。所有子节点都有一个共同的前缀,
上图的关键字集合{"分散","分散精力","分散投资","分布式","工程","工程师"} 。
Trie树的基本性质:
-
根节点是总入口,不存储字符,除根节点外的每一个子节点都包含一个字符。
-
从根节点到叶节点的完整路径是一个term。
-
从根节点到某个中间节点也可能是一个term,即一个term可能是另一个term的前缀。
-
每个节点的所有子节点包含的字符互不相同。
优点
-
插入和查询的效率很高,都为,其中 是待插入/查询的字符串的长度。
- 关于查询,会有人说 hash 表时间复杂度是不是更快?但是,哈希搜索的效率通常取决于 hash 函数的好坏,若一个坏的 hash 函数导致很多的冲突,效率并不一定比Trie树高。
-
Trie树中不同的关键字不会产生冲突。
-
Trie树只有在允许一个关键字关联多个值的情况下才有类似hash碰撞发生。
-
Trie树不用求 hash 值,对短字符串有更快的速度。通常,求hash值也是需要遍历字符串的。
-
Trie树可以对关键字按字典序排序。
缺点
-
当 hash 函数很好时,Trie树的查找效率会低于哈希搜索。
-
空间消耗比较大。
示例代码
package main
import "fmt"
// Node 子节点
type Node struct {
Word rune // 当前节点存储的字符。byte只能表示英文字符,rune可以表示任意字符
Child map[rune]*Node // 子节点,用一个map存储
Term string // 标记关键词
}
// TrieTree Trie树
type TrieTree struct {
root *Node
}
// add 把words[beginIndex:]插入到Trie树中
func (n *Node) add(words []rune, term string, beginIndex int) {
if beginIndex >= len(words) { // words已经遍历完
n.Term = term
return
}
if n.Child == nil {
n.Child = make(map[rune]*Node)
}
word := words[beginIndex] // 把这个word放到node的子节点中
if child, exists := n.Child[word]; !exists {
newNode := &Node{Word: word}
n.Child[word] = newNode
newNode.add(words, term, beginIndex+1) // 递归
} else {
child.add(words, term, beginIndex+1) // 递归
}
}
// walk words[0]就是当前节点上存储的字符,按照words的指引顺着树往下走,最终返回words最后一个字符对应的节点
func (n *Node) walk(words []rune, beginIndex int) *Node {
if beginIndex == len(words)-1 {
return n
}
beginIndex += 1
word := words[beginIndex]
if child, exists := n.Child[word]; exists {
return child.walk(words, beginIndex)
} else {
return nil
}
}
// traverseTerms 遍历一个node下面所有的term。注意要传数组的指针,才能真正修改这个数组
func (n *Node) traverseTerms(terms *[]string) {
if len(n.Term) > 0 {
*terms = append(*terms, n.Term)
}
for _, child := range n.Child {
child.traverseTerms(terms)
}
}
// AddTerm 添加关键词
func (t *TrieTree) AddTerm(term string) {
if len(term) <= 1 {
return
}
words := []rune(term)
if t.root == nil {
t.root = new(Node)
}
t.root.add(words, term, 0)
}
// Retrieve 检索关键词
func (t *TrieTree) Retrieve(prefix string) []string {
if t.root == nil || len(t.root.Child) == 0 {
return nil
}
words := []rune(prefix)
firstWord := words[0]
if child, exists := t.root.Child[firstWord]; exists {
end := child.walk(words, 0)
if end == nil {
return nil
} else {
terms := make([]string, 0, 100)
end.traverseTerms(&terms)
return terms
}
} else {
return nil
}
}
func main() {
t := new(TrieTree)
t.AddTerm("分散")
t.AddTerm("分散精力")
t.AddTerm("分散投资")
t.AddTerm("分布式")
t.AddTerm("工程")
t.AddTerm("工程师")
terms := t.Retrieve("分散")
fmt.Println(terms)
terms = t.Retrieve("人工")
fmt.Println(terms)
}
>>>>>>
[分散 分散投资 分散精力]
[]