0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何使用 go 实现红黑树

Linux爱好者 来源:Linux爱好者 作者:Linux爱好者 2022-03-21 11:54 次阅读

本文介绍了使用 go 实现红黑树。

二叉搜索树(二叉查找树)

二叉查找树也叫二叉搜索树,也叫二叉排序树,它具有以下特点:1. 如果左子树不为空,则左子树上的结点的值都小于根节点;2. 如果右子树不为空,则右子树上的结点的值都大于根节点;3. 子树同样也要遵循以上两点。

二叉树的遍历方式

二叉树的遍历方式:前序遍历、中序遍历、后序遍历和层序遍历(MySql)。

只要一棵树是二叉查找树,那么它的中序遍历(左根右输出)一定是有序的。比如看下边的一棵二叉树:

a132bac4-9330-11ec-952b-dac502259ad0.jpg

它的前序遍历(根左右)为:530468;
它的中序遍历(左根右)为:034568;
它的后序遍历(左右根)为:043865。

二叉搜索树有哪些应用?

既然是搜索树,那么它肯定就是用在查找上。我们来分析下它的查找时间复杂度:

先来看下面两颗二叉搜索树:

a143adac-9330-11ec-952b-dac502259ad0.jpg

查找时间复杂度其实就是树的深度。

上图一的时间复杂度:O(n)时间复杂度,表示需要查找 n 次,即循环 n 遍,退化成链表的情况。

上图二的时间复杂度(类似二分查找):logn,即 => x= => logn。

那么为什么会出现退化成链表的情况(图一)呢?我们该怎么处理才不会变成链表呢(怎么解决)?

当插入的节点数值从小到大时,则就会出现二叉树退化成链表的情况,那么有另一种树可以解决这种情况,就是平衡二叉树(AVL树)。

AVL树是一种追求极致平衡的二叉搜索树,即将树调整到最优的情况,由于这种树结构比较苛刻、旋转也比较多,这里就不重点展开讲。

红黑树

红黑树的由来

今天需要重点讲的是红黑树。红黑树的底层数据结构其实就是二叉查找树,也就是说红黑树是一颗特殊的二叉查找树,也叫自平衡二叉查找树

红黑树出现的目的就是上所讲到的,为了解决二叉树退化成链表的情况。

红黑树的演进过程

链表(暴力查处) -> 二叉树 -> 二叉查找树 -> 特殊的二叉查找树(自平衡的二叉查找树)

红黑色讲解

通过上面的例子以及两个图,我们发现二叉树的结构就决定了其搜索的效率及性能,那么我们需要怎么样才让二叉树达到优化的效果呢?

因此就有了红黑树了,我们看下图(红黑树):

a15f25c8-9330-11ec-952b-dac502259ad0.jpg

红黑树的性质:

每个节点不是红色就是黑色(这里不一定要红色和黑色,只要知道这里的红色和黑色是什么意思即可)

一定没有连在一起的红色节点

根节点(第一个节点)是黑色 root

每个红色节点的两个字节点都是黑色

叶子结点都是黑色(上图中其实还有为null的叶子结点没有画出来),如下图所示:

a1753dae-9330-11ec-952b-dac502259ad0.jpg

根据以上性质或者说满足了以上性质的树,就可以达到近似平衡了。

怎么判断一个节点是否为根结点?

没有父节点

入度为0的节点

红黑树变换规则

要满足这些性质,需要对树做什么操作呢?

为了红黑可以满足这些性质,因此出现了旋转,那么红黑树有几种变换规则呢?

有三种变换规则:

变色:红变黑 黑变红

左旋转

右旋转

左旋转示例:

a1892648-9330-11ec-952b-dac502259ad0.jpg

旋转和颜色变换规则:所有插入的点默认为红色; 1. 变颜色的情况:如果插入的节点的父节点和叔叔节点为红色,则:1)把父节点和叔叔节点设为黑色;2)把爷爷(祖父)节点设为红色;3)把指针定位到爷爷节点作为当前需要操作的节点,再根据变换规则来进行判断操作。2. 左旋:如果当前父节点是红色,叔叔节点是黑色,而且当前的节点是右子树时,则需要以父节点作为左旋转。3. 右旋:当前父节点是红色,叔叔节点是黑色,且当前的节点是左子树时,则:1)把父节点变为黑色;2)把爷爷节点变为红色;3)以父节点右旋转。

a19a2f92-9330-11ec-952b-dac502259ad0.jpg

比如要往上图中插入数字6,则这颗红黑色的演变过程如下:

step1: 插入6节点后如下图,它的父节点和叔叔节点均是红色,则需要根据变换规则来操作,到step2了。

a1a952c4-9330-11ec-952b-dac502259ad0.jpg

step2: 根据变换规则,需要将插入节点的父节点和叔叔节点均变为黑色,爷爷节点变为红色,然后将指针定位到爷爷节点(蓝色圈)。将指针定位到爷爷节点(12)后,此时做为当前需要操作的节点,再根据变换规则来判断,可以看到下图的当前节点(12)的叔叔节点是黑色的,则不能用变颜色规则的情况了,进行step3,此时需要进行左旋或右旋了。

a1b9a8fe-9330-11ec-952b-dac502259ad0.jpg

step3: 根据上图情况可以知道此时是符合左旋规则的:当前节点(12)的父节点(5)是红色,叔叔节点(3)是黑色,而且当前的节点是右子树。接下来需要进行左旋变换(三步走):

a1cd5ade-9330-11ec-952b-dac502259ad0.jpg

step4:左旋变换后,可以看到当前节点(5)的父节点(12)为红色,叔叔节点(30)为黑色,而且当前节点为左子树,符合右旋的规则。接下来就是进行右旋的变换操作了:1)把父节点(12)变为黑色;2)把爷爷节点(29)变为红色;3)以父节点(12)右旋转

a1dc228a-9330-11ec-952b-dac502259ad0.jpg

小结

到这里,可以看到经过多次旋转后,这棵树是符合红黑色的性质。

Golang代码实现红黑树

直接上代码,如下:

packagemain

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

const(
REDbool=true
BLACKbool=false
)

typeNodestruct{
keyint
valueinterface{}

left*Node
right*Node
//parent*Node

colorbool
}

typeRedBlackTreestruct{
sizeint
root*Node
}

funcNewNode(key,valint)*Node{
//默认添加红节点
return&Node{
key:key,
value:val,
left:nil,
right:nil,
//parent:nil,
color:RED,
}
}

funcNewRedBlackTree()*RedBlackTree{
return&RedBlackTree{}
}

func(n*Node)IsRed()bool{
ifn==nil{
returnBLACK
}
returnn.color
}

func(tree*RedBlackTree)GetTreeSize()int{
returntree.size
}

//nodex
///左旋转/
//T1x--------->nodeT3
////
//T2T3T1T2
func(n*Node)leftRotate()*Node{
//左旋转
retNode:=n.right
n.right=retNode.left

retNode.left=n
retNode.color=n.color
n.color=RED

returnretNode
}

//nodex
///右旋转/
//xT2------->ynode
////
//yT1T1T2
func(n*Node)rightRotate()*Node{
//右旋转
retNode:=n.left
n.left=retNode.right

retNode.right=n
retNode.color=n.color
n.color=RED

returnretNode
}

//颜色变换
func(n*Node)flipColors(){
n.color=RED
n.left.color=BLACK
n.right.color=BLACK
}

//维护红黑树
func(n*Node)updateRedBlackTree(isAddint)*Node{
//isAdd=0说明没有新节点,无需维护
ifisAdd==0{
returnn
}

//需要维护
ifn.right.IsRed()==RED&&n.left.IsRed()!=RED{
n=n.leftRotate()
}

//判断是否为情形3,是需要右旋转
ifn.left.IsRed()==RED&&n.left.left.IsRed()==RED{
n=n.rightRotate()
}

//判断是否为情形4,是需要颜色翻转
ifn.left.IsRed()==RED&&n.right.IsRed()==RED{
n.flipColors()
}

returnn
}

//递归写法:向树的root节点中插入key,val
//返回1,代表加了节点
//返回0,代表没有添加新节点,只更新key对应的value值
func(n*Node)add(key,valint)(int,*Node){
ifn==nil{//默认插入红色节点
return1,NewNode(key,val)
}

isAdd:=0
ifkey< n.key {
        isAdd, n.left = n.left.add(key, val)
    } else if key >n.key{
isAdd,n.right=n.right.add(key,val)
}else{
//对value值更新,节点数量不增加,isAdd=0
n.value=val
}

//维护红黑树
n=n.updateRedBlackTree(isAdd)

returnisAdd,n
}

func(tree*RedBlackTree)Add(key,valint){
isAdd,nd:=tree.root.add(key,val)
tree.size+=isAdd
tree.root=nd
tree.root.color=BLACK//根节点为黑色节点
}

//前序遍历打印出key,val,color
func(tree*RedBlackTree)PrintPreOrder(){
resp:=make([][]interface{},0)
tree.root.printPreOrder(&resp)
fmt.Println(resp)
}

func(n*Node)printPreOrder(resp*[][]interface{}){
ifn==nil{
return
}
*resp=append(*resp,[]interface{}{n.key,n.value,n.color})
n.left.printPreOrder(resp)
n.right.printPreOrder(resp)
}


//测试红黑树代码
funcmain(){
count:=10
redBlackTree:=NewRedBlackTree()
nums:=make([]int,0)
fori:=0;i< count; i++ {
        nums = append(nums, rand.Intn(count))
    }

    fmt.Println("source data: ", nums)
    now := time.Now()
    for _, v := range nums {
        redBlackTree.Add(v, v)
    }

    fmt.Println("redBlackTree:", now.Sub(time.Now()))
    redBlackTree.PrintPreOrder()
    fmt.Println("节点数量:", redBlackTree.GetTreeSize())
}

测试输出结果如下:

datasource:[1779185060]
redBlackTree:-2.136µs
[[77false][11true][00false][66false][55true][99false][88true]]
节点数量:7

总结

红黑树是保持近似平衡的二叉树,从另一种角度上来说红黑树不是平衡二叉树,它的最大高度为2*logn。

二分搜索树,AVL树,红黑树对比:1. 对于完全随机的数据源,普通二分搜索树很好用,缺陷是在极端情况下容易退化成链表 2. 对于查询较多的使用情况,AVL树很好用,因为他的高度一直保持h=logn 3. 红黑树牺牲了平衡性,即h=2*logn,但在添加和删除操作中,红黑树比AVL树有优势 4. 综合增删改查所有操作,红黑树的统计性能更优

原文标题:二叉树、红黑树以及Golang实现红黑树

文章出处:【微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

审核编辑:彭菁

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4841

    浏览量

    69196
  • 数据源
    +关注

    关注

    1

    文章

    63

    浏览量

    9732
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12396

原文标题:二叉树、红黑树以及Golang实现红黑树

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Go语言实现敏感词检测(前缀

    大家都知道游戏文字、文章等一些风控场景都实现了敏感词检测,一些敏感词会被屏蔽掉或者文章无法发布。今天我就分享用Go实现敏感词前缀来达到文本的敏感词检测,让我们一探究竟!
    发表于 10-31 10:22 1272次阅读

    验功放时将输出端柱短路,为什么会烧功放

    验功放时将输出端柱短路,为什么会烧功放
    发表于 08-26 18:54

    什么是“”看了就知道

    今天我们要说的就是就是一棵非严格均衡的二叉,均衡二叉又是在二叉搜索的基础上增加了自动
    发表于 10-27 17:00

    Linux下一种高性能定时器池的实现

    提出Linux用户空间下的一种高性能定时器池的实现方法。主要基于时间轮、及Linux内核提供了一种利于管理的定时器句柄Timerfd。结合
    发表于 09-25 14:57 25次下载

    一文详解

    界应用很广泛,比如 Java 中的 TreeMap,JDK 1.8 中的 HashMap、C++ STL 中的 map 均是基于树结构实现的。考虑到
    的头像 发表于 02-02 17:25 4280次阅读
    一文详解<b class='flag-5'>红</b><b class='flag-5'>黑</b><b class='flag-5'>树</b>

    详解电源二叉到底是什么

    作为数据结构的基础,分很多种,像 AVL 、二叉搜索....今天我想分享的是关于二
    的头像 发表于 06-06 15:05 1w次阅读
    详解电源二叉<b class='flag-5'>树</b>到底是什么

    魔3和鲨2买哪个好

    鲨2还是魔3?作为两款同样采用高通骁龙855移动平台的游戏手机,鲨2和魔3不免会被消费者放在一起进行比较。那么,鲨2和
    的头像 发表于 07-04 14:43 1.4w次阅读

    鲨2和魔3哪个好

    魔3和鲨2哪个好?和鲨科技今年上半年力推的“鲨2”一样,魔3也搭载了高通骁龙855移动平台。那么,
    的头像 发表于 06-30 09:20 2.1w次阅读

    魔Mars和鲨Helo哪个最好

    在18年的圈里,游戏手机燃起了很大的热点,尤其是鲨和魔更是打动很多手游爱好者,进化后的魔Mars和鲨Helo更是棋逢对手,如今令人期待的
    的头像 发表于 07-11 11:43 3667次阅读

    (Red Black Tree)是一种自平衡的二叉搜索

    平衡(Balance):就是当结点数量固定时,左右子树的高度越接近,这棵二叉越平衡(高度越低)。而最理想的平衡就是完全二叉/满二叉,高度最小的二叉
    的头像 发表于 07-01 15:05 5824次阅读
    <b class='flag-5'>红</b><b class='flag-5'>黑</b><b class='flag-5'>树</b>(Red Black Tree)是一种自平衡的二叉搜索<b class='flag-5'>树</b>

    Go并发模型的实现原理

    Go语言是为并发而生的语言,Go语言是为数不多的在语言层面实现并发的语言;也正是Go语言的并发特性,吸引了全球无数的开发者。
    的头像 发表于 04-15 08:49 1443次阅读

    是如何模拟2-3 B的操作逻辑的

    大家都听说过,也都知道很厉害,是计算机里面评价非常高的数据结构。但是每当想学习
    的头像 发表于 08-30 10:22 928次阅读

    Unitree Go2四足机器人:智能科技新选择

    7月12日,宇推出了备受瞩目的Unitree Go2四足机器人,售价从9997元起。这款产品将彻底改变以往消费级四足机器人的市场格局,并引领整个行业的变革。早在2021年6月,宇就发布了划时代
    的头像 发表于 07-13 16:48 1760次阅读

    为什么MySQL索引要用B+tree?

    是一种特化的 AVL(平衡二叉),都是在进行插入和删除操作时通过特定操作保持二叉查找
    发表于 10-30 14:41 282次阅读

    的特点及应用

    比起理解的原理,更重要的是理解的应用场景,因为某些应用场景的需要,
    的头像 发表于 11-10 11:16 828次阅读
    <b class='flag-5'>红</b><b class='flag-5'>黑</b><b class='flag-5'>树</b>的特点及应用