trie——字典树

package trie

import (
	"fmt"
	"math/rand"
	"testing"
	"time"
)

/*
前缀树
1.单个字符中,字符从前到后的加到一棵多叉数上
2.字符放在路上,节点是国内有专属的数据项(常见的是pass和end值[通过值和收尾值])  pass 当前节点被通过了几次, end 成为了多少个字符串的结尾
3.所有样本都这样添加,如果没有路就新建,如果有路就复用
4.沿途节点的pass值增加1,每个字符串结束时来到的节点end值加1

可以完成前缀相关的查询


["abc"], ["abd"], ["kst"]
 每新加一个字符串都是 从头节点触发的
 加一个节点[“ab"] ["a"]
                                                        [p:1,e:0]  p: 5  e:1
                                               a/                      \k
                                          [p:1, e:0] p:2 p:3        [p:1,e:0]
                                        b/                               \s
                                   [p:1,e:0]   p:2 p:3 e:1            [p:1,e:0]
                                  c/     \d                                \t
                          [p:1,e:1]     [p:1,e:1]                       [p:1,e:1]

     如果所有字符串字符数量是N  建树的代价是 O(N),每处理一个字符代价是O(1)
    "gab" 加入直接新建 不用考虑复用(压缩)
    查询abd 字符串插入了几次
    有没有走向 a 的路
    有没有走向 b 的路
    有没有走向 d 的路  查看e值

    查询"ks" 有k 没有s

    加入的所有字符串 有多少字符串以a 做前缀    p值就是 加入多少次,因为以我为路径加入的


  字典树可以用于 不文明发言检测

*/


type Node1 struct {
    pass  int
	end   int
	nexts []*Node1
}

func NewNode1() *Node1 {
	return &Node1{
		pass:  0,
		end:   0,
		// 0 --> 'a'  1 --> 'b'  nexts[i] == nil i方向上的路不存在
		//                       nexts[i] != nil i方向上的路存在
		nexts: make([]*Node1,26),  //如果加入的字符都是 a——z 小写的形式
	}
}

type  Trie1 struct {
	root *Node1
}

func NewTrie1() *Trie1 {
	return &Trie1{
		root: NewNode1(),
	}
}

func (t *Trie1)insert(word string)  {
	if word == "" {
		return
	}
	node := t.root
	node.pass++  // 有字符的话,必经过头结点

	path := 0
	for k := 0; k < len(word); k++ {
		path = int(word[k] - 'a')
		if node.nexts[path] == nil {
			node.nexts[path] = NewNode1()
		}
		node = node.nexts[path]
		node.pass++
	}
	node.end++
}

func (t *Trie1)delete(word string)  {
	if t.search(word) == 0 {   // 查一下是否被加入,不删除一个未加入的字符串
		return
	}

	node := t.root
	node.pass--

	path := 0
	for k := 0; k < len(word); k++ {
		path = int(word[k] - 'a')
		if node.nexts[path].pass - 1 == 0 {
			node.nexts[path] = nil
			return
		}
		node.nexts[path].pass--
		node = node.nexts[path]
	}
	node.end--
}



func (t *Trie1)search(word string) int { // word 被加入几次
	if word == "" {
		return 0
	}

	node, path := t.root, 0
	for k := 0; k < len(word); k++ {
		path = int(word[k] - 'a')
		if node.nexts[path] == nil {
			return 0
		}
		node = node.nexts[path]
	}
	return node.end
}

func (t *Trie1)prefixNumber(pre string) int { // pre 所有加入的字符串中,有几个是以pre这个字符串作为前缀
	if pre == "" {
		return 0
	}

	node, path := t.root, 0
	for k := 0; k < len(pre); k++ {
		path = int(pre[k] - 'a')
		if node.nexts[path] == nil {
			return 0
		}
		node = node.nexts[path]
	}
	return node.pass
}





func TestTrieInsert(t *testing.T)  {
	trie := NewTrie1()
	trie.insert("helloworld")
	fmt.Println(*trie.root.nexts['h'- 'a'].nexts['e'-'a'].nexts['l'-'a'])
}

func TestTrieSearch(t *testing.T)  {
	trie := NewTrie1()
	trie.insert("helloworld")
	trie.insert("helloworld")
	fmt.Println(trie.search("helloworld"))
}

func TestTriePrefixNumber(t *testing.T)  {
	trie := NewTrie1()
	trie.insert("helloworld")
	trie.insert("helloworld")
	trie.insert("hellow")
	fmt.Println(trie.prefixNumber("hello"))
}

func TestTrieDelete(t *testing.T)  {
	trie := NewTrie1()
	trie.insert("helloworld")
	trie.insert("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
}



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

type Node2 struct {
	pass int
	end  int
	nexts map[int]*Node2
}

func NewNode2() *Node2 {
	return &Node2{
		pass:  0,
		end:   0,
		nexts: map[int]*Node2{},
	}
}

type Trie2 struct {
	root *Node2
}


func NewTrie2() *Trie2 {
	return &Trie2{
		root: NewNode2(),
	}
}

func (t *Trie2)insert(word string)  {
	if word == "" {
		return
	}
	node := t.root
	node.pass++

	path := 0
	for k := range word { //range 好处,可以不拆分汉字
		path = int(word[k])
		if  node.nexts[path] == nil {
			node.nexts[path] = NewNode2()
		}
		node = node.nexts[path]
		node.pass++
	}
	node.end++
}

func (t *Trie2)delete(word string)  {
	if t.search(word) == 0 {   // 查一下是否被加入,不删除一个未加入的字符串
		return
	}

	node := t.root
	node.pass--

	path := 0
	for k := range word {
		path = int(word[k])
		if node.nexts[path].pass - 1 == 0 {
			node.nexts[path] = nil
			return
		}
		node.nexts[path].pass--
		node = node.nexts[path]
	}
	node.end--
}

func (t *Trie2)search(word string) int { // word 被加入几次
	if word == "" {
		return 0
	}

	node, path := t.root, 0
	for k := range word {
		path = int(word[k])
		if  node.nexts[path] == nil {
			return 0
		}
		node = node.nexts[path]
	}
	return node.end
}

func (t *Trie2)prefixNumber(pre string) int { // pre 所有加入的字符串中,有几个是以pre这个字符串作为前缀
	if pre == "" {
		return 0
	}

	node, path := t.root, 0
	for k := range pre {
		path = int(pre[k])
		if  node.nexts[path] == nil {
			return 0
		}
		node = node.nexts[path]
	}
	return node.pass
}


func TestTrieInsert2(t *testing.T)  {
	trie := NewTrie2()
	trie.insert("helloworld")
	fmt.Println(*trie.root.nexts['h'].nexts['e'].nexts['l'])
}

func TestTrieSearch2(t *testing.T)  {
	trie := NewTrie2()
	trie.insert("helloworld")
	trie.insert("helloworld")
	fmt.Println(trie.search("helloworld"))
}

func TestTriePrefixNumber2(t *testing.T)  {
	trie := NewTrie2()
	trie.insert("helloworld")
	trie.insert("helloworld")
	trie.insert("hellow")
	fmt.Println(trie.prefixNumber("hello"))
}

func TestTrieDelete2(t *testing.T)  {
	trie := NewTrie2()
	trie.insert("helloworld")
	trie.insert("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
	trie.delete("helloworld")
	fmt.Println(trie.prefixNumber("hello"))
}



func TestTrie(t *testing.T)  {
   dic := []string{
	   "卧了个*",
	   "傻*",
	   "出门没吃药",
	   "违规词",
	   "尼玛,没吃药吗",
	   "长眼睛了吗",
   }

   trie := NewTrie2()
   for k := range dic{
	   trie.insert(dic[k])
   }

   userSay := "尼玛"
   if trie.prefixNumber(userSay) > 0 {
	   //违规词汇
   }

   fmt.Println(trie.search("卧了个槽"))
}

func Test(t *testing.T)  {
  compare()
}





func getRandomString(l int) string {
	rand.Seed(time.Now().UnixNano())
	length := rand.Int() % l
	ans := ""
	for i := 0; i < length; i++ {
		str := string(rune('a' + (rand.Int() % 26)))
		ans += str
	}
	return ans
}




func compare()  {
	t1, t2 := NewTrie1(), NewTrie2()
	for i := 1; i < 100000;i++ {
		randStr := getRandomString(100)
		t1.insert(randStr)
		t2.insert(randStr)

		key := getRandomString(10)
		if t1.search(key) != t2.search(key) {
			fmt.Println(randStr,t1.search(key),t2.search(key))
			panic("err")
		}

		if t1.prefixNumber(key) != t2.prefixNumber(key) {
			fmt.Println(randStr,t1.prefixNumber(key),t2.prefixNumber(key))
			panic("err")
		}

		del := getRandomString(10)
		t1.delete(del)
		t2.delete(del)
	}

	fmt.Println((*t1.root).nexts)
	fmt.Println(t2.root.nexts)

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值