Trie

    Trie树的三个基本性质,很简单,网上随便一篇博客都可以查出来,罗列如下:

        1、根节点不包含字符,除根节点外每一个节点都只包含一个字符。

        2、从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

        3、每个节点的所有子节点包含的字符都不相同。

    应用的场景就是查询字符串,字符串的前缀,词频统计等等。优点是查询的效率非常高,缺点是利用空间换取时间。

    实现原理:利用Trie树节点组织成一棵树,每个节点包含两个值,一个代表该字符的添加次数,另一个代表以该字符结尾的字符串个数。路径上用于存储字符。存储字符可以利用数组指针也可以利用哈希结构实现。

    具体实现:(pass:上述节点值的前者,end:上述节点值的后者)

func (this *Trie) Insert(s string) {

	if s == "" {
		return
	}

	node := &this.root

	for i := 0; i < len(s); i++ {

		index := s[i] - 'a'

		_, ok := node.next[index]

		if !ok {
			newNode := NewTrieNode()
			node.next[index] = &newNode
		}
		node = node.next[index]
		node.pass++
	}
	node.end++
}

插入算法:从root节点出发,遍历访问待添加的字符串的每个字符,如果没有这个字符就添加,有的话直接更新遍历字符的pass值,遍历完成后更新结尾字符节点的end值。

func (this *Trie) Search(s string) int32 {

	if s == "" {
		return -1
	}

	node := &this.root
	for i := 0; i < len(s); i++ {

		index := s[i] - 'a'

		_, ok := node.next[index]

		if !ok {
			return 0
		}

		node = node.next[index]
	}

	return node.end
}

查找字符串算法:从root节点开始,遍历访问待查询的字符串的每个字符,如果没有某个字符的信息,说明没有该字符串,直接返回0,字符串遍历结束后的话直接返回节点的end值。

func (this *Trie) PrefixNum(s string) int32 {

	if s == "" {
		return -1
	}

	node := &this.root
	for i := 0; i < len(s); i++ {

		index := s[i] - 'a'

		_, ok := node.next[index]

		if !ok {
			return 0
		}

		node = node.next[index]
	}

	return node.pass
}

查询字符串前缀算法:和查询字符串算法基本一致,只是最后返回的是节点的pass值。

func (this *Trie) Delete(s string) {

	if s == "" || this.Search(s) < 1 {
		return
	}

	node := &this.root
	for i := 0; i < len(s); i++ {

		index := s[i] - 'a'

		toDelPass := node.next[index]
		toDelPass.pass--
		if toDelPass.pass == 0 {
			delete(node.next, index)
			return
		}

		node = node.next[index]
	}
	node.end--
}
删除字符串算法:首先查看树中是否有待删除的字符串,没有的话直接返回,有的话从root节点开始,遍历访问待删除的字符串的每个字符,更新节点的pass值,如果更新后的pass值为0剩下的就不用遍历了,因为剩下的就是待删除的字符串的剩余字符节点,没必要做无用功。说白了删除算法中,重要的就是提前结束的问题。如果遍历可以结束的话,更新结尾节点的end值。



    

你可能感兴趣的:(数据结构和算法)