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)
}

>>>>>>
[分散 分散投资 分散精力]
[]
posted @ 2022-02-19 22:14  自己有自己的调调、  阅读(389)  评论(0编辑  收藏  举报