Trie-字典树/前缀树
最近在看Gin 的源码。Gin的高性能依靠前缀树,每一个分支代表一个路由而树的节点中则可以存放该路由的执行中间件以及handle控制器。
前言
字典树,是一种树形结构。主要思想是利用字符串的公共前缀来节约存储空间。
抽象应用场景
利用几个基本单元的按顺序排列组合来表示更多的单元。例如单词的表示,网站的网址……
字典树优点
- 节约空间:例如我们用字典树去存储英语单词,我们不需要针对每个单词而重复地存储组成他们的字母。
- 快速检索: 要从字典树中检索某个单词时,我们只需要按照单词的顺序依次检索,而每个节点的子节点用Map进行存储。因此,用单词树进行检索的效率很高。
Go实现字典树
package main
import "fmt"
// 单词树节点的结构体
type trieNode struct {
isWord bool
next map[rune]*trieNode
}
// 单词树的结构体
type trie struct {
size int
root *trieNode
}
// 初始化一颗单词树
func InitTrie() *trie {
return &trie{
0,
&trieNode{
false, make(map[rune]*trieNode),
},
}
}
// 获得单词树的size
func (t *trie) GetSize() int { return t.size }
// 向单词树中添加单词
// 若成功添加则树的深度 + 1
func (t *trie) Add(word string) {
if len(word) == 0 {
return
}
cur := t.root
t.size = t.size + cur.insert(word)
}
// 递归的像树中插入单词
func (node *trieNode) insert(word string) int {
_, ok := node.next[rune(word[0])]
if !ok {
node.next[rune(word[0])] = &trieNode{
false,
map[rune]*trieNode{},
}
}
node = node.next[rune(word[0])]
if len(word) == 1 {
if !node.isWord {
node.isWord = true
return 1
}
return 0
}
return node.insert(word[1:])
}
// 输出一个节点的下的单词
func (node *trieNode) show(prex string) {
if node.isWord {
fmt.Printf("%s\n", prex)
}
for k, _ := range node.next {
node.next[k].show(prex + string(k))
}
}
// 是否包含这个路径
func (t *trie) ContainsWordPath(word string) (bool, *trieNode) {
if len(word) == 0 {
return false, nil
}
cur := t.root
for _, v := range word {
t1, ok := cur.next[v]
if !ok {
return false, nil
}
cur = t1
}
return true, cur
}
// 单词树是否包含某个单词
func (t *trie) Contains(word string) bool {
containsPath, cur := t.ContainsWordPath(word)
return containsPath && cur.isWord
}
// 是否包含该前缀的单词
func (t *trie) IsPrefix(word string) bool {
containsPath, _ := t.ContainsWordPath(word)
return containsPath
}
// 输出单词树中的所有单词
func (t *trie) Show() {
if t.size == 0 {
return
}
t.root.show("")
}
func test() {
tree := InitTrie()
tree.Add("ywhtest")
tree.Add("ywab")
tree.Add("asdsa")
tree.Show()
}
func main() {
test()
}
output
ywhtest
ywab
asdsa
在这个案例中我们使用字典树存储单词,并将字典树中的单词按行进行输出。