1. 程式人生 > 其它 >Go語言資料結構與演算法-Trie樹

Go語言資料結構與演算法-Trie樹

Trie樹

概述

Trie樹,又叫字典樹、字首樹(Prefix Tree)、單詞查詢樹或鍵樹,是一種很常用的樹結構【多叉樹】。

它被廣泛用於各個方面,比如字串檢索、中文分詞、求字串最長公共字首和字典排序等等。

核心思想

空間換時間:資料結構本身比較消耗空間。所有子節點都有一個共同的字首,但它利用了字串的共同字首(Common Prefix)作為儲存依據,以此來節省儲存空間,並加速搜尋時間。

Trie 的字串搜尋時間複雜度為 O(m),m 為字串的長度,其查詢效能與集合中的字串的數量無關。其在搜尋字串時表現出的高效,使得特別適用於構建文字搜尋和詞頻統計等應用。

通過上圖展示得到Trie樹關鍵字集合{"分散", "分散精力", "分散投資", "分散式", "工程", "工程師"}

Trie樹的基本性質

  • 根節點不包含字元,除根節點外的每一個子節點都包含一個字元。
  • 從根節點到某一個節點,路徑上經過的字元連線起來,為該節點對應的字串。
  • 每個節點的所有子節點包含的字元互不相同。

示例程式碼

package main

import "fmt"

// Node 子節點
type Node struct {
	World 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(worlds []rune, term string, beginIndex int) {
	// worlds遍歷完成,記錄關鍵詞
	if beginIndex == len(worlds) {
		n.Term = term
		return
	}

	if n.Child == nil {
		n.Child = make(map[rune]*Node)
	}

	word := worlds[beginIndex]
	if child, ok := n.Child[word]; !ok {
		// 子節點不存在word,把word放到node子節點中
		newNode := &Node{World: word}
		n.Child[word] = newNode
		// 遞迴遍歷
		newNode.Add(worlds, term, beginIndex+1)
	} else {
		// 遞迴遍歷
		child.Add(worlds, term, beginIndex+1)
	}
}

// Walk worlds[0]當前節點上儲存的字元,按照words的指引順著樹往下走,最終返回words最後一個字元對應的節點
func (n *Node) Walk(worlds []rune, beginIndex int) *Node {
	if beginIndex == len(worlds)-1 {
		return n
	}
	beginIndex += 1
	word := worlds[beginIndex]
	if child, ok := n.Child[word]; ok {
		return child.Walk(worlds, beginIndex)
	} else {
		return nil
	}
}

// TraverseTerms 拿到節點的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, ok := t.root.Child[firstWord]; ok {
		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() {
	tree := new(TrieTree)
	tree.AddTerm("分散")
	tree.AddTerm("分散精力")
	tree.AddTerm("分散投資")
	tree.AddTerm("分散式")
	tree.AddTerm("工程")
	tree.AddTerm("工程師")
	
	terms := tree.Retrieve("分散")
	fmt.Println(terms)
	terms = tree.Retrieve("人工")
	fmt.Println(terms)
}

>>>>>>
[分散 分散精力 分散投資]
[]