go语言简单实现 前缀树(Trie)与了解基数树(Radix-Tree)

前言

目录结构

Trie 
	Trie.go
main.go

Trie 的实现

  • 实现写法, 可以参照模仿 container/list 双向链表的写法实现
  • 下面代码中使用的是 slice 存储子节点, 线性查找遍历, 后面会进行 hash优化。
package Trie

// 字典树:
/*
	1. 单词结尾表示:需要使用一个 bool 类型,辨识当前节点是否是结尾字符
		叶子结点是 单词结尾
		非叶子节点 也可能是单词的结尾
	2. 实现功能
		增加单词
		搜索单词
*/


// 定义树的节点
type Node struct {
	// 1. 保存当前字符
	C byte
	// 2. 若干个子节点
	Children []*Node
	// 3. 标识节点是否是一个单词最后的一个字符
	isWord bool
}

// 给子节点 列表添加新的节点
func (n *Node) Add(c byte) {
	// 构造节点
	node := Node{
		C: c,
	}
	// 定义的 slice 类型的,不用初始化,也可以正常使用append方法
	n.Children = append(n.Children, &node)
}

// 返回子节点 列表 长度
func (n *Node) Len() int {
	return len(n.Children)
}

// 设置节点的 isword
func (n *Node) SetIsword(flag bool) {
	n.isWord = flag
}

// 整棵树的根节点
type Trie struct {
	// 字典树的根节点
	Root *Node
}


// 随便初始化根节点为 "/" 吧
func (t *Trie) Init() *Trie {
	t.Root = &Node{
		'/',
		make([]*Node, 0),
		false,
	}
	return t
}


// 外部调用初始化一个 字典树
func New() *Trie { return new(Trie).Init() }


// 添加单词
// 时间复杂度:
func (t *Trie) Add(word string) {
	// 转换为 字符
	w := []byte(word)
	// 找到每一个节点
	cur := t.Root
	// 遍历 word的每一个字符
	for _, c := range w {
		// 1. 查找子节点中是否包含当前字符
		index := t.containsChar(c, cur.Children)
		if index == -1 {
			// 添加新节点
			cur.Add(c)
			// 更新cur 指向它
			index = cur.Len() - 1
		}
		// cur 指向下一个子节点
		cur = cur.Children[index]
	}
	// 将最后一个节点字符的 尾设置为 true
	cur.SetIsword(true)
}


// 查找子节点中是否包含指定字符: 私有方法
func (t *Trie) containsChar(c byte, childrens []*Node) int {
	// 线性遍历查找
	for i, node := range childrens {
		// 查找到既返回索引
		if node.C ==  c {
			return i
		}
	}
	return -1
}


// 判断是否包含指定的单词
// 不仅能找到单词,并且单词的结尾必须是 isWord = true 才说明有这个单词
func (t *Trie) Contains(word string) bool {
	// 转换为 字符
	w := []byte(word)
	// 找到每一个节点
	cur := t.Root
	// 遍历 word的每一个字符
	for _, c := range w {
		// 1. 查找子节点中是否包含当前字符
		index := t.containsChar(c, cur.Children)
		if index == -1 {
			return false
		}
		// cur 指向下一个子节点
		cur = cur.Children[index]
	}
	// 判断最后一个单词是否是 单词末尾 cur.isWord == true,
	// 直接缩写为 cur.isWord
	return cur.isWord
}


使用hash优化效率,改造代码线性查找

package Trie

// 字典树:
/*
	1. 单词结尾表示:需要使用一个 bool 类型,辨识当前节点是否是结尾字符
		叶子结点是 单词结尾
		非叶子节点 也可能是单词的结尾
	2. 实现功能
		增加单词
		搜索单词
*/


// 定义树的节点
type Node struct {
	// 1. 保存当前字符
	//C byte
	// 使用hash存储,就不需要当前的字符了,它已经在key中了
	// 2. 若干个子节点: 字符: 节点
	Children map[byte]*Node
	// 3. 标识节点是否是一个单词最后的一个字符
	isWord bool
}

// 给子节点 列表添加新的节点
func (n *Node) Add(c byte) {
	// 构造节点:  nil 类型 map 不可直接使用添加元素等
	node := Node{
		Children: make(map[byte]*Node, 0),
	}
	// 天啊及
	n.Children[c] = &node
}

// 返回子节点 列表 长度
//func (n *Node) Len() int {
//	return len(n.Children)
//}

// 设置节点的 isword
func (n *Node) SetIsword(flag bool) {
	n.isWord = flag
}

// 整棵树的根节点
type Trie struct {
	// 字典树的根节点
	Root *Node
}


// 随便初始化根节点为 "/" 吧
func (t *Trie) Init() *Trie {
	t.Root = &Node{
		make(map[byte]*Node, 0),
		false,
	}
	return t
}


// 外部调用初始化一个 字典树
func New() *Trie { return new(Trie).Init() }


// 添加单词
// 时间复杂度:
func (t *Trie) Add(word string) {
	// 转换为 字符
	w := []byte(word)
	// 找到每一个节点
	cur := t.Root
	// 遍历 word的每一个字符
	for _, c := range w {
		// 1. 查找子节点中是否包含当前字符
		// 如果不包含, 添加一下
		if _, ok := cur.Children[c]; !ok {
			// 添加新节点
			cur.Add(c)
		}
		// cur 指向下一个子节点
		cur = cur.Children[c]
	}
	// 将最后一个节点字符的 尾设置为 true
	cur.SetIsword(true)
}


 查找子节点中是否包含指定字符: 私有方法
//func (t *Trie) containsChar(c byte, childrens []*Node) int {
//	// 线性遍历查找
//	for i, node := range childrens {
//		// 查找到既返回索引
//		if node.C ==  c {
//			return i
//		}
//	}
//	return -1
//}


// 判断是否包含指定的单词
// 不仅能找到单词,并且单词的结尾必须是 isWord = true 才说明有这个单词
func (t *Trie) Contains(word string) bool {
	// 转换为 字符
	w := []byte(word)
	// 找到每一个节点
	cur := t.Root
	// 遍历 word的每一个字符
	for _, c := range w {
		// 1. 查找子节点中是否包含当前字符
		if _, ok :=  cur.Children[c]; !ok {
			return false
		}
		// cur 指向下一个子节点
		cur = cur.Children[c]
	}
	// 判断最后一个单词是否是 单词末尾 cur.isWord == true,
	// 直接缩写为 cur.isWord
	return cur.isWord
}


运行main方法,执行测试用例

package main

import (
	"01/Trie字典树/Trie"
	"fmt"
)

func main() {
	// 创建字典树
	t := Trie.New()
	// 编写测试
	t.Add("big")
	t.Add("pat")
	t.Add("bigger")
	t.Add("dog")
	t.Add("door")
	// 测试
	fmt.Println(t.Contains("big"))
	fmt.Println(t.Contains("bigg"))
	fmt.Println(t.Contains("bigger"))
	fmt.Println(t.Contains("biggerr"))
}

  • 选择不同的数据结构,实现不同的算法,对性能和空间的提升是有的,并且缩短了代码量。

基数树:

  • 前面的前缀树可以看出来, 查找速度是跟 树的深度有关的。 并且从在单一分支的情况,且对单一超长的 word或者是比较稀疏的单词的时候,存储很费力不讨好,会导致树总是只有单个分支,大大提高树的深度 和 存储空间。
  • 扩展到许多框架的路由匹配
  • 应对大量的路由匹配, radix 树可以提高你应对大量匹配的能力。
    在这里插入图片描述
    在这里插入图片描述
  • 这篇文章有个简单的介绍还可以, 可以看下,演进过程。
  • https://ethbook.abyteahead.com/ch4/radix.html
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值