赞
踩
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。
Tree 树 是利用空间复杂度作为代价换区时间复杂度的应用,查找复杂度 为 o(Len),空间复杂度 是比较大的。
下面这个图是一个由 RA RC RZ RAC RAD RACE 等等组成 红色的节点 被标记成 了一个单词的结尾。
Tire用于统计:
题目:给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置。
解法 :从第一个单词开始构造Tire树,Tire树包含两字段,字符与位置,对于非结尾字符,其位置标0,结尾字符,标注在100000个单词词表中的位置。对词表建造Tire树,对每个词检索到词尾,若词尾的数字字段>0,表示单词已经在之前出现过,否则建立结尾字符标志,下次出现即可得知其首次出现位置,便利词表即可依次计算出每个单词首次出现位置复杂度为O(N×L)L为最长单词长度,N为词表大小
Tire用于排序
题目:对10000个英文名按字典顺序排序
解法:建造Tire树,先序便利即可得到结果。
type trieNode struct {
isWord bool//是否单词结尾
next map[byte][]*trieNode //子节点
}
type Trie struct{
size int //大小
root *trieNode //根节点
}
首先 我们定义了 trie 节点 的基本数据结构,每个节点记录了 所有子节点的指针。 Trie 结构 定义了 大小 和 根节点。
首先 我们构造了一颗 Trie树,初始化Root节点。
//初始化 Trie树
func NewTrie() *Trie{
node := trieNode{
isWord: false,
next: make(map[byte][]*trieNode,0),
}
a := Trie{
size: 0,
root: &node,
}
return &a
}
结构很简单 就像 一个链表一样 只不过 有点区别 就是 每个节点 都有多个 子节点。
增删改查 我们先实现 增加:
这里面使用了非递归的方式,首先得到 root 下面的 所有节点,然后 遍历 节点
找不到 就新建 找到了 把 当前节点指针cur 改成 下一个节点指针,然后 如果是插入到最后一个字符了 ,就把 isWord 修改成True
//往 节点内 添加内容 func(trie *Trie) Insert(content string){ node := trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } if content == ""{ panic("error!") } if trie.root == nil{ trie.root = &node } rootnode := trie.root.next var cur *trieNode for i,v := range []byte(content){ var tmpnode *trieNode //如果cur为 nil if cur == nil{ if rootnode[v] == nil{ tmpnode = &trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } //如果是字符串 结尾 if len(content) -1 == i{ tmpnode.isWord = true } cur = tmpnode rootnode[v] = cur }else{ cur = rootnode[v] } //如果 cur 已经被初始化 }else { if cur.next[v] == nil{ tmpnode = &trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } //如果是字符串 结尾 if len(content) -1 == i{ tmpnode.isWord = true } cur.next[v] = tmpnode cur = tmpnode }else{ //如果是字符串 结尾 if len(content) -1 == i{ cur.isWord = true } cur = cur.next[v] } } } }
让我们整理下思路,要查找 是否包含指定字符串
先定义一个 指针 记录当前节点
var cur *node
然后遍历 要查找的字符串
for i,key range := str
第一次 当前节点 就直接从根节点拿,
if cur == nil
cur == root.next
否则的话 从 子节点列表 取得 然后 根新为当前节点
else
cur = cur.next[key]
如果 cur 为 空 的话 表明 没找到
然后 如果 找到 字符串最后一个节点,但是字符串没有被 设置为 单词 就认为 没有找到。
上面的步骤其实很简单,比起插入来说。当然 如果用递归 可以跟简介一些。但其实 也差不多。下面给出代码。
func (trie *Trie) Contains(content string) bool{ if trie.root == nil{ panic("error nil") } var cur *trieNode //当前节点指针 for i,v := range []byte(content){ if cur == nil{ //第一次 从root的next子节点获取 cur = trie.root.next[v] }else{ //当前节点 的next子节点里获取 cur = cur.next[v] } //如果 没有 获取到 认为 没有 if cur == nil{ return false } //如果已经 到达了最后一个节点 但是这个节点没有被标记成 单词 if len(content) -1 == i && cur.isWord == false{ return false } } //如果 上面条件都不满足 则认为 找到了 return true }
这里我们用 递归 比较方便。 其实就是递归搜索,一直搜索 遍历完 所有 节点 然后 把每个节点记录的 字符 传递到下一个节点,如果是 单词就打印。
func deepSearchx(root *trieNode,deep int,res []byte) { if root.isWord{ //如果 是一个单词 打印 fmt.Println(string(res)) } for k,v := range root.next{ //搜索深度 if deep <= 0{ return } tmp := make([]byte,0,0) tmp = append(tmp,res...) tmp =append(tmp,k) //不停递归传递 切片 deepSearchx(v, deep -1, tmp) } }
下面 是 我测试了下 代码:
有了上面的代码 要实现这个 是不是跟 玩一样。。。。
分解步骤 只需要两步:
第一步 找到 那个单词的根节点
第二部 判断 是不是单词,如果不是 我们不需要修改返回false 如果 是 单词 修改返回true
这里 我们 参考 ,Contains的代码做一点小修改就好了。
其实 如果 简单的话加上一行删除就好了。但是 如果删除了树 是不是 得回收原来的空间 。所以 我们需要在添加一个方法 递归 把 没用的节点删除了 这个 可以参考Search 的代码 .
这里用递归的思路
思路是这样的,如果 每个节点 判断 他下面么有挂载单词的节点 了 就认为 下面的 都没用了 那么就设置为 nil。
只要递归玩得好,代码实现 也是很简单的
//将被删除的节点清除掉 func adjust(root *trieNode,ptr *trieNode,key byte,deep int){ //如果 没有next了 并且 这个节点 没有 记录单词 if len(root.next) == 0 && root.isWord == false{ deepSearchforDelete(ptr,nil) delete(ptr.next,key) return }else if len(root.next) == 0{ return } //如果 是 单词记录节点 for k,v := range root.next{ if deep == 0{ key = k } //记录下这个节点 if root.isWord { ptr = root //记录父节点 key = k//记录子节点 key } adjust(v,ptr,key,deep + 1) } }
这里每次遇到单词节点 就记录下,如果 到达最后一个节点且最后一个节点 没有 isword 那么 就从 上一个 isword 处的next删除这一段。这里 我们 递归 有 3个参数 一个 不用多说 就是递归子节点,另 2 各参数 是用来保存 上一个 单词节点的 子节点,然后是要删除的key。
这里我们也实现下吧,其实 还是很简单的
首先是查找节点,然后把节点下所有的 单词打出来 这两个功能在 前面 已经分别实现过了。只要 把它们组合在一起就好啦。
/** * Created by @CaomaoBoy on 2020/3/17. * email:<115882934@qq.com> */ type trieNode struct { isWord bool//是否单词结尾 next map[byte]*trieNode //子节点 } type Trie struct{ size int //大小 root *trieNode //根节点 } //初始化 Trie树 func NewTrie() *Trie{ node := trieNode{ isWord: false, next: make(map[byte]*trieNode,26), } a := Trie{ size: 0, root: &node, } return &a } //如果存在插入 func(trie *Trie) InsertIfContains(content string) bool{ if !trie.Contains(content){ trie.Insert(content) return true } return false } //往 节点内 添加内容 func(trie *Trie) Insert(content string){ if content == ""{ panic("error!") } if trie.root == nil{ node := trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } trie.root = &node } rootnode := trie.root.next var cur *trieNode for i,v := range []byte(content){ var tmpnode *trieNode //如果cur为 nil if cur == nil{ if rootnode[v] == nil{ tmpnode = &trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } //如果是字符串 结尾 if len(content) -1 == i{ tmpnode.isWord = true trie.size ++ } cur = tmpnode rootnode[v] = cur }else{ cur = rootnode[v] } //如果 cur 已经被初始化 }else { if cur.next[v] == nil{ tmpnode = &trieNode{ isWord: false, next: make(map[byte]*trieNode,0), } //如果是字符串 结尾 if len(content) -1 == i{ tmpnode.isWord = true trie.size ++ } cur.next[v] = tmpnode cur = tmpnode }else{ cur = cur.next[v] //如果是字符串 结尾 if len(content) -1 == i{ cur.isWord = true trie.size ++ return } } } } } //是否包含 str func (trie *Trie) Contains(content string) bool{ if trie.root == nil{ panic("error nil") } var cur *trieNode for i,v := range []byte(content){ if cur == nil{ //第一次 从root的next子节点获取 cur = trie.root.next[v] }else{ //当前节点 的next子节点里获取 cur = cur.next[v] } //如果 没有 获取到 认为 没有 if cur == nil{ return false } //如果已经 到达了最后一个节点 但是这个节点没有被标记成 单词 if len(content) -1 == i && cur.isWord == false{ return false } } return true } //遍历查找所有节点内容 func(trie *Trie) Search(deep int){ if trie.root == nil{ panic("root nil") } //res := make([][]byte,0) //deepSearch(trie.root,0,&res) //for _,v := range res{ // fmt.Println(string(v)) //} deepSearchx(trie.root,deep,nil) } 深度优先遍历 //func deepSearch(root *trieNode,deep int,res *[][]byte) []byte{ // if root.isWord{ // return nil // } // for k,v := range root.next{ // tmp := make([]byte,0,0) // tmp= append(tmp, k) // tmp = append(tmp, deepSearch(v, deep+1, res)...) // if deep == 0 { // *res = append(*res, tmp) // continue // }else{ // fmt.Println(string(tmp)) // return tmp // } // // // } // return nil //} //深度优先遍历 func deepSearchx(root *trieNode,deep int,res []byte) { if root.isWord{ fmt.Println(string(res)) } for k,v := range root.next{ //搜索深度 if deep == 0 && deep != -1{ return } tmp := make([]byte,0,0) tmp = append(tmp,res...) tmp =append(tmp,k) deepSearchx(v, deep -1, tmp) } } //用来输出显示 func deepSearchforDelete(root *trieNode,res []byte) { if len(root.next) == 0{ fmt.Println("clear:",string(res)) } for k,v := range root.next{ tmp := make([]byte,0,0) tmp = append(tmp,res...) tmp =append(tmp,k) deepSearchforDelete(v, tmp) } } //是否包含 str func (trie *Trie) Delete(content string) bool{ if trie.root == nil{ panic("error nil") } var cur *trieNode for i,v := range []byte(content){ if cur == nil{ //第一次 从root的next子节点获取 cur = trie.root.next[v] }else{ //当前节点 的next子节点里获取 cur = cur.next[v] } //如果 没有 获取到 认为 没有 if cur == nil{ return false } //如果已经 到达了最后一个节点 但是这个节点没有被标记成 单词 if len(content) -1 == i && cur.isWord == false{ return false } } cur.isWord = false//找到了 就删除 trie.Adjust()//删除没用节点 return true } //将被删除的节点清除掉 func(trie *Trie) Adjust(){ adjust(trie.root,trie.root,0,0) } //将被删除的节点清除掉 func adjust(root *trieNode,ptr *trieNode,key byte,deep int){ //如果 没有next了 并且 这个节点 没有 记录单词 if len(root.next) == 0 && root.isWord == false{ deepSearchforDelete(ptr,nil) delete(ptr.next,key) return }else if len(root.next) == 0{ return } //如果 是 单词记录节点 for k,v := range root.next{ if deep == 0{ key = k } //记录下这个节点 if root.isWord { ptr = root //记录父节点 key = k//记录子节点 key } adjust(v,ptr,key,deep + 1) } } //自动提示 func(trie *Trie) Suggess(content string){ if trie.root == nil{ panic("error nil") } var cur *trieNode for i,v := range []byte(content){ if cur == nil{ //第一次 从root的next子节点获取 cur = trie.root.next[v] }else{ //当前节点 的next子节点里获取 cur = cur.next[v] } //如果 没有 获取到 认为 没有 if cur == nil{ fmt.Println("no srarch") } //如果已经 到达了最后一个节点 但是这个节点没有被标记成 单词 if len(content) -1 == i { deepSearchx(cur,-1,nil) } } } func main(){ t := NewTrie() t.Insert("caomao") t.Insert("triemap") t.Insert("tEST00001") t.Insert("tEST0") t.Insert("SADASDASDSADASD") t.Search(20) t.Insert("apple"); t.Insert("app"); fmt.Println(t.Contains("app")) fmt.Println(t.Contains("tEST0")) fmt.Println(t.Contains("apple")) t.Delete("app") fmt.Println(t.Contains("apple")) t.Suggess("tE") }
初步测试了下,代码应该没什么大问题,由于 随便写的 逻辑应该没问题,代码比较粗糙,随便写的 也没借鉴别人的代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。