go实现trie前缀树结构,敏感字关键字查找效率优化

1.问题

现在项目有一个类似机器人问答的功能,是根据所有配置关键字组成一个map,然后在玩家输入想查找的功能时根据关键字遍历所有已经配置关键字,找到则返回,未找到则需要遍历所有关键字,效率不高

2.解决方案

1.通过资料了解到高效查找关键字的方法,可以通过实现一个trie结构(前缀树)来提高查找速度

trie结构原理

1.根节点不包含字符,除根节点以外每个节点只包含一个字符,并且都有一个标记来表示是否是一个关键字结尾

2.父节点的前缀必然是子节点的前缀

3.从根节点到某一个叶子节点,路径上经过的字符连接起来,为某个关键字对应的字符串

如图

对于插入方法

1.获取根节点作为当前节点

2.遍历关键字,查找以当前字符为节点的子节点

3.如果当前字符是当前节点的子节点的字符,则把子节点作为当前节点,重复第2步

4.如果不是,新建一个节点,新建节点的字符设置为当前字符,并且把新建节点设置为当前节点的子节点,继续第3步

对于查找方法

1.从第一个字符开始遍历玩家输入的关键字语句,获取树的根节点作为当前节点

2.遍历第1步获得的下标至关键字结尾,如果当前字符是当前节点的子节点,则判断当前字符是否为结尾字符,是的话证明找到了一个子串,可加入返回列表,把当前节点子节点作为当前节点。

3.如果不是当前节点的子节点,继续第1步和第2步

对于删除方法

1.获取根节点作为当前节点

2.遍历关键字,查找以当前字符为节点的子节点

3.如果找到当前字符节点,则删除字符节点包含的此关键字id,并且把子节点置为当前节点,回到第二步继续

4.如果没找到当前字符节点,则后面的也不会再有,结束循环,退出

以下附上代码,根据项目功能需求不同,节点的定义可以有所不同,但实现方法大致一样

package main

import (
    "fmt"
)
type trieNode struct {
    char rune
    idList []uint64 //只保存以此字符作为结尾的id
    idListAll []uint64 //保存所有包含此字符的id,删除时候用
    children map[rune]*trieNode
}

func NewTrieNode(char rune) *trieNode {
    return &trieNode {
        char : char,
        idList : make([]uint64, 0),
        idListAll : make([]uint64, 0),
        children : make(map[rune]*trieNode),
    }
}

type Trie struct {
    root *trieNode
}

func NewTrie() *Trie {
    trieNode := NewTrieNode(0)
    return &Trie{trieNode}
}

func Contains(s []uint64, e uint64) bool {
    for _, v := range s {
        if v == e {
            return true
        }
    }
    return false
}

func DeleteSlice(s []uint64, e uint64) ([]uint64) {
    index := 0
    for _, v := range s {
        if v == e {
            continue
        }

        s[index] = v
        index++
    }
    return s[ : index]
}

func (t *Trie) InsertNode(word string, wordid uint64) {
    node := t.root
    for _, code := range word {
        value, ok := node.children[code]
        if !ok {
            value = NewTrieNode(code)
            node.children[code] = value
        }
        node = value
        if !Contains(node.idListAll, wordid) {
            node.idListAll = append(node.idListAll, wordid)
        }
    }
    if !Contains(node.idList, wordid) {
        node.idList = append(node.idList, wordid)
    }
}

func (t *Trie) GetContains(word string) ([]uint64) {
    retindex := make([]uint64, 0)
    for index, _ := range word {
        node := t.root
        for _, code := range word[index : ] {
            value, ok := node.children[code]
            if !ok {
                break
            }
            for _, id := range value.idList {
                if Contains(retindex, id) {
                    continue
                }
                retindex = append(retindex, id)
            }
            node = value
        }
    }
    fmt.Println("本次查找结果 查找:", word, " 结果:", retindex)
    return retindex
}

func (t *Trie) DeleteNode(word string, id uint64) (bool) {
    node := t.root
    for _, code := range word {
        value, ok := node.children[code]
        if !ok {
            fmt.Println("未找到要删除的关键字 ", word, id)
            return false
        }

        value.idList = DeleteSlice(value.idList, id)
        value.idListAll = DeleteSlice(value.idListAll, id)
        if len(value.idList) == 0 && len(value.idListAll) == 0 {
            value.char = 0 //代表删除当前节点了
        }
        node = value
    }
    fmt.Println("删除的关键字成功 ", word, id)
    return true
}

func main() {
	trie := NewTrie()
    trie.InsertNode("你好", 1)
    trie.InsertNode("pvp", 2)
    trie.InsertNode("pve", 2)
    trie.InsertNode("装备打造", 3)
    trie.InsertNode("装备分解", 3)
    trie.InsertNode("装备强化", 3)

    trie.GetContains("你好啊")
    trie.GetContains("你好像刘翔")
    trie.GetContains("pvp怎么玩")
    trie.GetContains("怎么玩pve呢")
    trie.GetContains("装备打造有什么用")
    trie.GetContains("装备分解怎么分解")
    fmt.Println("\n")

    trie.DeleteNode("你好", 1)
    trie.DeleteNode("pve", 2)
    trie.DeleteNode("装备分解", 3)
    fmt.Println("\n")
    
    //删除之后再查找就找不到了
    trie.GetContains("你好啊")
    trie.GetContains("装备分解怎么分解")
    trie.GetContains("怎么玩pve呢")
    fmt.Println("\n")
    //没删除的还能找到
    trie.GetContains("pvp怎么玩")
    trie.GetContains("装备打造有什么用")
}

运行结果:

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值