【树结构】Trie字典树

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010853261/article/details/88808556

概念

Trie树的名字有很多,比如字典树,前缀树等等。

下面我们有and,as,at,cn,com这些关键词,那么如何构建trie树呢?
在这里插入图片描述
从上面的图中,我们或多或少的可以发现一些好玩的特性。

  第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
  第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。
  第三:每个单词的公共前缀作为一个字符节点保存。

使用场景:

(1)词频统计:
可能有人要说了,词频统计简单啊,一个hash或者一个堆就可以打完收工,但问题来了,如果内存有限呢?对于百亿级别的数据样本还能这么玩吗?所以这里我们就可以用trie树来压缩下空间,因为公共前缀都是用一个节点保存的。

(2)前缀匹配:
就拿上面的图来说吧,如果我想获取所有以"a"开头的字符串,从图中可以很明显的看到是:and,as,at,如果不用trie树, 你该怎么做呢?很显然朴素的做法时间复杂度为O(N2) ,那么用Trie树就不一样了,它可以做到h,h为你检索单词的长度,可以说这是秒杀的效果。

举个例子:现有一个编号为1的字符串”and“,我们要插入到trie树中,采用动态规划的思想,将编号”1“计入到每个途径的节点中, 那么以后我们要找”a“,”an“,”and"为前缀的字符串的编号将会轻而易举。

实现

1. 数据模型

为了方便,我也采用纯英文字母,我们知道字母有26个,那么我们构建的trie树就是一个26叉树,每个节点包含26个子节点。

/**
字典树
*/
type TrieNode struct {
	/* 子节点 */
	childs []*TrieNode

	freq int

	nodeChar byte
}

2. 添加字符串

既然是26叉树,那么当前节点的后续子节点是放在当前节点的哪一叉中,也就是放在childNodes中哪一个位置,这里我们采用 int k = word[0] - 'a’来计算位置。

func addNode(root *TrieNode, word string) {
	if len(word) == 0 {
		return
	}
	//找到word的第一个字符在root节点的第k个子节点
	k := word[0] - 'a'

	if root.childs[k] == nil {
		root.childs[k] = &TrieNode{
			freq:     1,
			nodeChar: word[0],
			childs:   make([]*TrieNode, 26),
		}
	}
	nextWord := word[1:]
	if len(nextWord) == 0 {
		root.childs[k].freq++
		return
	}
	addNode(root.childs[k], nextWord)
}

3. 删除操作

删除操作中,我们不仅要删除该节点的字符串编号,还要对词频减一操作。

func deleteNode(root *TrieNode, word string) {
	if len(word) == 0 {
		return
	}
	//找到word的第一个字符在root节点的第k个子节点
	k := word[0] - 'a'
	if root.childs[k] == nil {
		return
	}
	nextWord := word[1:]
	if len(nextWord) == 0 && root.childs[k].freq > 0 {
		root.childs[k].freq--
	}
	deleteNode(root.childs[k], nextWord)
}

4. 获取单词频率

/**
获取word出现的频率;
频率大于0表示存在,如果不存在频率为0
*/
func get(root *TrieNode, word string) int {
	if len(word) == 0 {
		return 0
	}
	k := word[0] - 'a'
	if root.childs[k] == nil {
		return 0
	}
	nextWord := word[1:]
	if len(nextWord) == 0 {
		return root.childs[k].freq
	}
	return get(root.childs[k], nextWord)
}

5. 测试

func GetRandomString(l int) string {
	str := "abcdefghijklmnopqrstuvwxyz"
	bytes := []byte(str)
	result := []byte{}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < l; i++ {
		result = append(result, bytes[r.Intn(len(bytes))])
	}
	return string(result)
}

//----------------------------

package trie_tree

import (
	"fmt"
	"testing"
	"time"
)

func prepareData() *TrieNode {
	root := &TrieNode{
		freq:     -1,
		nodeChar: 0,
		childs:   make([]*TrieNode, 26),
	}
	for i := 0; i < 1000000; i++ {
		addNode(root, GetRandomString(32))
	}
	return root
}

func TestTrie(t *testing.T) {
	root := prepareData()
	str := GetRandomString(32)
	addNode(root, str)
	fmt.Println(str)
	start := time.Now().UnixNano()
	freq := get(root, str) - 1
	end := time.Now().UnixNano()
	fmt.Println(freq)
	fmt.Println(end - start)
}

没有更多推荐了,返回首页