使用平衡树 进行运势计算: 架构一只树

1,979 阅读4分钟

本文已参与 「新人创作礼」 活动,一起开启掘金创作之路。

写在前面

二叉树是 树形结构计算的一种特例,二叉平衡树是其中的佼佼者,当我们使用有序的方法插入节点到树时,相比链表的查询,操作大量数据时 我们可以省却在链表中查找的繁琐。

二叉搜索树是 红黑树的基础红黑树 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)

            }

    }
    
    

小结

好了,一颗正宗的树就此建立完成,我们将以此结构来做一些运算和分析,当然,也就是运势推断。

有兴趣的可以看这个小系列的第二节。

本文已参与 「新人创作礼」 活动,一起开启掘金创作之路。