红黑树的实现和讲解(一)

1 篇文章 0 订阅
1 篇文章 0 订阅

 前言:

  最近去公司实习了就很少更新博客了,公司用的是go语言。码农只能够跟着大佬,大佬说用什么语言就用什么语言。所以这次红黑树的实现就用go语言写也当练练手了。废话不多说马上进入主题!红黑树是一种自平衡的查找二叉树。它的插入、查找和删除操作的时间复杂度都是O(logn)。


一.红黑树的特点

  1. 节点是红色或黑色。 

  2. 根节点是黑色。

  3 每个叶节点(NIL节点,空节点)是黑色的。 

  4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) 

  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。


二 构建红黑树

   

  2.1 节点 

   我们把红黑树的节点叫为RBNode 

   有如下属性:

     1.节点颜色:颜色不是红就是黑所以这里我们用bool型,红为true,黑色为black。

     2.节点数据:我们运行节点可以存储各种类型数据只要该类型实现了Entryer接口

     3.节点的左右子和父节点


      颜色的定义:

      

const (
	RED   bool = true
	BLACK bool = false
)

   接口定义

  

type Entryer interface {
	GetValue() interface{}
	Compare(Entryer) int
}

节点定义

type RBNode struct {
	entry               Entryer
	color               bool
	parent, left, right *RBNode
}


 2.2 红黑树结构

 树的结构比较简单只需要记录着根节点即可

 

type RBTree struct {
	root *RBNode
}

三 红黑树的插入操作

 红黑树的增加操作最能体现出红黑树的自平衡性。在插入节点的过程中会可能出现不平衡的现象,这个时候就要旋转做平衡操作,我们先谈谈红黑树插入过程是怎么保持平衡又调整的

  首先一个新的的节点是红色的左右子树和父节点都为空,我们定义构建新节点的方法,传人节点数据返回新节点

func NewRBNode(entry Entryer) *RBNode {
	rbNoe := &RBNode{
		entry:  entry,
		color:  RED,
		parent: nil,
		left:   nil,
		right:  nil,
	}
	return rbNoe
}


接下来我们讨论插入过程中可能出现的问题

总得来说插入过程中会出现两个大类

1.父节点为黑色

 

就如上图的情况,这种情况下是不需要调整的。这种情况是完全符合上述的5条性质,不需要调整。所以插入的过程首先考虑父节点是否为黑色。

2.父节点为红色的时候

 父节点同样为红色的时候这个时候就要调整了,因为违背了性质4红色节点的子节点都是黑色的。在父节点同样是红色的情况下由可以分为两种类型 

 2.1 叔节点同样是红色

 

就如上图的情况,在叔节点同样是红色的情况下只需要做变色即可以完成局部调整。因为n的那边子树多了一个红色那么我们将父节点和叔节点变成黑色,这个时候就不违背性质4了。但是整体上多了一个黑色节点,所以将g变成红色。所以局部调整完成了。局部上子树平衡了,G变色红色后继续递归调用检查G为红色的时候是否违背性质4等这里要递归调用交给下一层处理

2.2 叔节点为黑色

 这种情况就比较复杂了,因为叔节点为黑色的时候,很明显叔节点的那一支的子树是缺了一个节点。这个时候就要进行右旋或左旋,在做旋转操作前先检查爷父子树的结构是否存在“之”字型结构,如下图就是之字型结构


即子节点为父节点的左节点的时候父节点为祖父节点的右节点(右侧则相反)

这个时候就要先做旋转操作先破坏“之”字型结构



我们定义子节点为父节点的右子树时出现“之”字形称为右之(左相反),当出现右之的时候,我们要将子节点同样变成是父节点的左节点,所以要进行以父节点为轴右旋操作。左之的时候就要父节点为轴左旋操作,操作结果如上图。


经过上面的调整后,我们破坏了之字型的结构,那么我们就可以将之字型结构的情况下经过破坏后变成非之字型结构。那么我们就可以当作非之字型结构一样处理。这个时候就比较简单,因为父节点和子节点都同为祖父节点的同一侧字树如下图:


若为同左侧的时候如上图第一种情况,那么就以祖父节点为轴进行右旋。同右侧相反。经过旋转操作后将原来是父节点的节点变成黑色,将原来是祖父节点的节点变成红色。经过旋转和变色操作后红黑树就平衡了。


四代码实现

 我们讨论了插入情况下这么多种情况,那么现在就以代码展现出来各种情况下如何处理。

 4.1 旋转操作的实现

在上述过程中我们频繁地出现旋转操作,所以我们先变成节点选择操作的代码,该方法是以自己为轴作为旋转操作,在旋转过程中可能出现根节点的改变,若根节点改变了则返回新的根节点,若没有改变这返回空。我们默认左旋的时候一定有右孙节点,在调用的过程时候要保证。

  4.1.2 右旋

  

//右旋参数为旋转轴的节点 若根节点变动返回根节点
func (rbNode *RBNode) rightRotate() *RBNode {
	var root *RBNode
	if rbNode == nil {
		return root
	}
	if rbNode.left == nil {
		return root
	}
	parent := rbNode.parent
	var isLeft bool
	if parent != nil {
		isLeft = parent.left == rbNode
	}
	grandson := rbNode.left.right
	if grandson != nil {
		grandson.parent = rbNode
	}
	rbNode.left.right = rbNode
	rbNode.parent = rbNode.left
	rbNode.left = grandson
	// 判断是否换了根节点
	if parent == nil {
		rbNode.parent.parent = nil
		root = rbNode.parent
	} else {
		if isLeft {
			parent.left = rbNode.parent
		} else {
			parent.right = rbNode.parent
		}
		rbNode.parent.parent = parent
	}
	return root
}

  4.1.2 左旋

//左旋参数为旋转轴的节点 若根节点变动返回根节点
func (rbNode *RBNode) leftRotate() *RBNode {
	var root *RBNode
	if rbNode == nil {
		return root
	}
	if rbNode.right == nil {
		return root
	}
	parent := rbNode.parent
	var isLeft bool
	if parent != nil {
		isLeft = parent.left == rbNode
	}
	grandson := rbNode.right.left
	if rbNode.right.left != nil {
		rbNode.right.left.parent = rbNode
	}
	rbNode.right.left = rbNode
	rbNode.parent = rbNode.right
	rbNode.right = grandson
	// 判断是否换了根节点
	if parent == nil {
		rbNode.parent.parent = nil
		root = rbNode.parent
	} else {
		if isLeft {
			parent.left = rbNode.parent
		} else {
			parent.right = rbNode.parent
		}
		rbNode.parent.parent = parent
	}
	return root
}


4.2 树的插入实现

 在插入的时候要先判断树是否为空如果为空则,新节点就是根节点,若不为空则先插入然后检查是否平衡

 树插入

 

//插入操作
func (rbTree *RBTree) Insert(entry Entryer) {
	if rbTree.root == nil {
		root := NewRBNode(entry)
		rbTree.insertCheck(root)
		return
	}
	rbTree.insertNode(rbTree.root, entry)
}

节点插入

  节点插入比较简单不需要复述啰嗦,插入后要检查是否平衡

//插入节点
func (rbTree *RBTree) insertNode(pNode *RBNode, entry Entryer) {
	res := pNode.entry.Compare(entry)
	if res != 1 {
		if pNode.left != nil {
			rbTree.insertNode(pNode.left, entry)
		} else {
			temp := NewRBNode(entry)
			temp.parent = pNode
			pNode.left = temp
			rbTree.insertCheck(temp)
		}
	} else {
		if pNode.right != nil {
			rbTree.insertNode(pNode.right, entry)
		} else {
			temp := NewRBNode(entry)
			temp.parent = pNode
			pNode.right = temp
			rbTree.insertCheck(temp)
		}
	}
}

插入检查:我们在检查的时候调整树

//检查插入
func (rbTree *RBTree) insertCheck(pNode *RBNode) {
	parent := pNode.parent
	if parent == nil {
		pNode.color = BLACK
		rbTree.root = pNode
		return
	}
	//父亲节点为红色则要处理
	if parent.color == RED {
		uncle := pNode.getUncle()
		if uncle != nil && uncle.color == RED {
			uncle.color = BLACK
			parent.color = BLACK
			parent.parent.color = RED
			rbTree.insertCheck(parent.parent)
		} else {
			grandParent := pNode.getGrandParent()
			if grandParent.left == parent {
				if parent.right == pNode {
					parent.leftRotate() //父节点先左旋
				}
				if root := grandParent.rightRotate(); root != nil {
					rbTree.root = root
				}
			} else {
				if parent.left == pNode {
					parent.rightRotate() //父节点先右旋
				}
				if root := grandParent.leftRotate(); root != nil {
					rbTree.root = root
				}
			}
			grandParent.color = RED
			parent.color = BLACK
		}
	}
}


总结:我们讨论了插入过程中出现的各种情况已经红黑树的插入代码,以后继续深入红黑树的删除操作。源代码在github上,有兴趣的可以去看看先。

https://github.com/520123zxc/RedBlackTree.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值