本文已参加 「新人创造礼」 活动,一同敞开创造之路。
写在前面
二叉树是 树形结构核算的一种特例,二叉平衡树是其间的佼佼者,当咱们使用有序的办法刺进节点到树时,相比链表的查询,操作大量数据时 咱们可以省却在链表中查找的繁琐。
二叉搜索树是 红黑树的基础红黑树 R-B tree, 全称 Red-Black Tree 本身是一个 二叉查找树,在其基础上附加了两个要求树中每个热点增加一个用于存储颜色的 标志域。
树中没有一个 路径比其他任何路径长出两倍,整个树要接近于 平衡 状态。 本文试着使用树形结构存储运势核算中数据的存储使命。
1 怎么开始
咱们需求一个树,这个树的结点需求可以记得自己的key和数据payload, 左右子节点,父节点等信息。所以咱们先规划节点,记得把平衡因子带上。
type TreeNode struct {
Key int
Payload string
LeftChild *TreeNode
RightChild *TreeNode
Parent *TreeNode
balanceFactor int
}
如此,咱们开始了万里长征(其实是只要3大步的战略)第一步,万事开头难,也没有那么难,不是吗?
可是只要这些节点并不能完结咱们的使命,咱们需求一个办理它们的办法,也便是咱们将要把这些节点,组织成树的方式。
而且在此基础上,咱们需求增加一些信息,比如左数多大,右树多大,总共多大(感觉有些多余,可是为了便利取得,仍是记载吧),由于每个节点都知道其周围的信息,办理树只需求记载根节点信息。
// 树办理
type BinarySearchTree struct {
Root *TreeNode //根节点
Size int //本树总巨细 包含根节点
LeftSize int //左子树巨细
RightSize int //右子树巨细
}
1.1 咱们还需求什么?
关于树的每个节点,咱们需求一系列办法以知晓其周围的信息,比如左子节点有没有? 是什么? 是不是叶子节点? 同样的包含右子节点也是如此,另外咱们还需求对整个树有个了解,所以需求有办法对其进行遍历 和核算。
func (tn *TreeNode) HasLeftChild() *TreeNode {
return tn.LeftChild
}
func (tn *TreeNode) HasRightChild() *TreeNode {
return tn.RightChild
}
func (tn *TreeNode) IsLeftChild() bool {
/// 是否左子 节点
if tn.Parent != nil && tn.Parent.LeftChild == tn {
return true
}
return false
}
func (tn *TreeNode) IsRightChild() bool {
// 是否 右子节点
if tn.Parent != nil && tn.Parent.RightChild == tn {
return true
}
return false
}
func (tn *TreeNode) IsRoot() bool {
// 是否根节点
if tn.Parent == nil {
return true
} else {
return false
}
}
func (tn *TreeNode) IsLeaf() bool {
// 是否叶子节点
if tn.RightChild == nil && tn.LeftChild == nil {
return true
} else {
return false
}
}
1.2 再进一步的
咱们还需求对树进行遍历以核算和分析,
咱们还需求对树进行查找和删去
….
这样吧,咱们可以开辟一个缓存,用以存储这些信息
// 缓存队列,用于存放 二叉树的 中序遍历结果
type CacheChan struct {
Size int /// cache 巨细标记
Read <-chan *TreeNode
Input chan<- *TreeNode
}
func NewCacheChan(size int) *CacheChan {
if size <= 0 {
panic("size must bigger than 0")
}
Chans := make(chan *TreeNode, size)
return &CacheChan{
Size: size,
Read: Chans,
Input: Chans,
}
}
对缓存的操作
// 增加一个节点到缓存
func (ch *CacheChan) Putin(td *TreeNode) int {
MutilLock.Lock()
defer MutilLock.Unlock()
if len(ch.Input) < ch.Size {
ch.Input <- td
return ch.Size
}
return 0
}
// 按序取出一个node 节点
func (ch *CacheChan) GetNode() *TreeNode {
MutilLock.Lock()
defer MutilLock.Unlock()
if len(ch.Read) > 0 {
td := <-ch.Read
return td
}
return nil
}
func (ch *CacheChan) IsTreeNodeIn(key int) bool {
isIn := false
if ch.Size <= 0 {
return isIn
}
t := ch.Size
for i := 0; i < t; i++ {
td := ch.GetNode()
ch.Putin(td)
if td.Key == key {
isIn = true
}
}
return isIn
}
咱们供给一种办法,以便知道该树的某个节点所有的子节点, 中序遍历 二叉树节点的子节点 并存储到channel 回来指向 channel的指针
func (tn *TreeNode) IterCache(size int) *CacheChan {
cacheCahn := tn.MakeCacheChan(size)
if tn != nil {
tnLeft := tn.HasLeftChild() // 左子树
if tnLeft != nil {
cacheCahn.Putin(tnLeft)
cacheCahn = IterCacheLeftNode(cacheCahn, tnLeft)
}
Logg.Printf("%+v\n", tn)
cacheCahn.Putin(tn)
tnRight := tn.HasRightChild() // 右子树
if tnRight != nil {
cacheCahn.Putin(tnRight)
cacheCahn = IterCacheRightNode(cacheCahn, tnRight)
}
}
return cacheCahn
}
现在咱们供给办法,写入节点到树中
// 刺进节点
func (bst *BinarySearchTree) Put(key int, val string, cNode *TreeNode) {
if key < cNode.Key {
// 假如参数key比当时节点key 小,进入树的左子树进行递归刺进
if cNode.HasLeftChild() != nil {
bst.Put(key, val, cNode.LeftChild) /// 递归左子树 刺进
} else {
cNode.LeftChild = &TreeNode{Key: key,
Payload: val, Parent: cNode} //树的左子节点
}
} else { /// 假如参数 key的值 大于当时节点key,进入树的右子树进入递归刺进
if cNode.HasRightChild() != nil {
bst.Put(key, val, cNode.RightChild) /// 递归右子树
} else {
cNode.RightChild = &TreeNode{Key: key,
Payload: val, Parent: cNode}
}
}
}
// 高度log2_n,假如key 列表随机分布,大于小于根节点的key的键值 大致适当
//
// 功能在于二叉树的高度,最大层次,高度也受数据项key刺进顺序影响
// 算法复杂度 最差 O(log2_n)
func (bst *BinarySearchTree) Puts(key int, val string) bool {
if bst.Root != nil {
// 有根节点
MutilLock.Lock()
defer MutilLock.Unlock()
if bst.Searcher(key) != nil {
// 现已存在 无法刺进
msg := fmt.Sprintf("the key had exist at treenode:%+v\n", key)
Logg.Println(msg)
return false
}
bst.Put(key, val, bst.Root)
//更新巨细信息
if bst.Root.Key < key {
//新 key 增加在右侧
bst.RightSize += 1
} else if bst.Root.Key >= key {
//新 key 增加在右侧
bst.LeftSize += 1
}
} else {
/// 没有根节点
bst.Root = &TreeNode{Key: key, Payload: val}
}
bst.Size += 1
return true
}
// 设置节点
func (bst *BinarySearchTree) SetNode(node *TreeNode) bool {
return bst.Puts(node.Key, node.Payload)
}
// 找到节点为key的 Payload值,只要是平衡树,get的时刻复杂度可用坚持在 O(logN)
func (bst *BinarySearchTree) Searcher(key int) *TreeNode {
if bst.Root != nil {
res := bst.Get(key, bst.Root) /// 递归该树
if res != nil {
return res
}
}
return nil
}
// 当时节点,即要刺进的 二叉查找树, 子树的根,为当时节点
func (bst *BinarySearchTree) Get(key int, cNode *TreeNode) *TreeNode {
if cNode == nil {
return nil
} else if cNode.Key == key {
return cNode
} else if key < cNode.Key {
return bst.Get(key, cNode.LeftChild)
} else {
return bst.Get(key, cNode.RightChild)
}
}
供给查找最大最小节点的办法
// 当时节点的左子节点,左子树的 最左下角的 值
func (tn *TreeNode) FindMin() *TreeNode {
current := tn // 根节点
for current.HasLeftChild() != nil { // 直到找到最左下角的值,便是直接后继
current = current.LeftChild
}
return current
}
// 当时节点的右子节点,右子树的 最右下角的 值
func (tn *TreeNode) FindMax() *TreeNode {
current := tn // 根节点
for current.HasRightChild() != nil { // 直到找到最右下角的值,便是直接后继
current = current.RightChild
}
return current
}
最后,咱们供给删去节点的办法,以便在核算运势时使用它制作爻变
// delete 的具体完结,要求仍然坚持BST 性质
// 1 节点无子节点 2 节点有1个子节点 3 节点有2个子节点
func (bst *BinarySearchTree) remove(cNode *TreeNode) {
if cNode.IsLeaf() {
/// leaf 叶子节点,没有子节点,属于场景1,无子节点,直接删去
if cNode == cNode.Parent.LeftChild {
/// 本身是 左子节点
cNode.Parent.LeftChild = nil
} else {
cNode.Parent.RightChild = nil
}
} else if cNode.HasBothChildren() {
/// 有两个子节点
succe := cNode.FindSuccessor() // 找到当时需求删去的节点的后继节点
succe.SpliceOut()
cNode.Key = succe.Key // 替换Key
cNode.Payload = succe.Payload // 替换Payload 值,节点的数据
} else {
/// 有一个子节点
if cNode.HasLeftChild() != nil {
if cNode.IsLeftChild() {
/// 左子节点删去
cNode.LeftChild.Parent = cNode.Parent // 修正指针。当时节点的左子节点的父节点,修正为节点的父节点
cNode.Parent.LeftChild = cNode.LeftChild // 修正指针,当时节点的父节点的左子节点,修正为当时节点的左子节点
} else if cNode.IsRightChild() {
/// 右 子节点删去
cNode.LeftChild.Parent = cNode.Parent
cNode.Parent.RightChild = cNode.LeftChild
} else {
// 根节点删去
cNode.ReplaceNodeData(
cNode.LeftChild.Key,
cNode.LeftChild.Payload,
cNode.LeftChild.LeftChild,
cNode.LeftChild.RightChild,
)
}
} else {
if cNode.IsLeftChild() {
/// 左子节点删去
cNode.RightChild.Parent = cNode.Parent
cNode.Parent.LeftChild = cNode.RightChild
} else if cNode.IsRightChild() {
/// 右子节点删去
cNode.RightChild.Parent = cNode.Parent
cNode.Parent.RightChild = cNode.RightChild
} else {
/// 根节点删去
cNode.ReplaceNodeData(
cNode.RightChild.Key,
cNode.RightChild.Payload,
cNode.RightChild.LeftChild,
cNode.RightChild.RightChild,
)
}
}
}
}
// deletes 用于删去 树中某个节点,子节点替换当时节点,具体是调用 delete办法
// 修正树的 巨细信息
func (bst *BinarySearchTree) Deletes(key int) {
var nTRemove *TreeNode
if bst.Size > 1 {
nTRemove = bst.Get(key, bst.Root)
if nTRemove != nil {
bst.remove(nTRemove)
bst.Size -= 1
} else {
msg := "Error, key not in tree"
panic(msg)
}
} else if bst.Size == 1 && bst.Root.Key == key {
nTRemove = bst.Root
bst.Root = nil
bst.Size -= 1
} else {
msg := "Error, key not in tree."
panic(msg)
}
//没有报错 则更新巨细
if bst.Root.Key > key {
//此key在左子树
bst.LeftSize -= 1
Logg.Printf("success del nTRemove:%v bst.LeftSize :%v \n", nTRemove, bst.LeftSize)
} else if bst.Root.Key <= key {
// 此key 在右子树
bst.RightSize -= 1
Logg.Printf("success del nTRemove:%v bst.RightSize :%v \n", nTRemove, bst.RightSize)
}
}
小结
好了,一颗正宗的树就此建立完结,咱们将以此结构来做一些运算和分析,当然,也便是运势揣度。
有爱好的可以看这个小系列的第二节。
本文已参加 「新人创造礼」 活动,一同敞开创造之路。