树结构——红黑树​原理及实现

目录

@[toc](文章目录)

#一、前言

#二、​红黑树的定义

#三、红黑树结点API

##1.左旋

##2.右旋

###i.向单个2-结点中插入新键

###ii.向底部的2-结点插入新键

##3.颜色反转

###i.向一棵双键树(即一个3-结点)中插入新键

##4.根结点的颜色总是黑色

###i.向树底部的3-结点插入新键

##5.代码实现

一、前言

二、​红黑树的定义

三、红黑树结点API

1.左旋

2.右旋

 i.向单个2-结点中插入新键

 ii.向底部的2-结点插入新键

3.颜色反转

i.向一棵双键树(即一个3-结点)中插入新键

4.根结点的颜色总是黑色

i.向树底部的3-结点插入新键

 5.代码实现


一、前言

在前面我使用图解的方式,让大家初步了解了2-3树,可以看到 2-3 树能保证在插入元素之后,树依然保持平衡状态,它的最坏情况下所有子结点 都是2- 结点,树的高度为 lgN, 相比于我们普通的二叉查找树,最坏情况下树的高度为 N ,确实保证了最坏情况下的 时间复杂度,但是2-3 树实现起来过于复杂,所以我们介绍一种 2-3 树思想的简单实现:红黑树。
红黑树主要是对 2-3 树进行编码,红黑树背后的基本思想是用标准的二叉查找树 ( 完全由 2- 结点构成 ) 和一些额外的信 息( 替换 3- 结点 ) 来表示 2-3 树。我们将树中的链接分为两种类型:
红链接: 将两个 2- 结点连接起来构成一个 3- 结点;
黑链接: 则是 2-3 树中的普通链接
确切的说,我们将 3- 结点表示为由由一条 左斜 的红色链接 ( 两个 2- 结点其中之一是另一个的左子结点 ) 相连的两个 2- 结点。这种表示法的一个优点是,我们无需修改就可以直接使用标准的二叉查找树的get 方法。

二、​红黑树的定义

红黑树是含有红黑链接并满足下列条件的二叉查找树:
1. 红链接均为左链接;
2. 没有任何一个结点同时和两条红链接相连;
3. 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同;
下面是红黑树与 2-3 树的对应关系:

三、红黑树结点API

因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们可以在之前的 Node 结点中添加一个布 尔类型的变量color 来表示链接的颜色。如果指向它的链接是红色的,那么该变量的值为 true ,如果链接是黑色 的,那么该变量的值为false

 在最终给大家看代码实现之前,大家有没有想过在红黑树增删修改的过程中,会不会出现一些问题,那这些问题我们应该如何处理这么问题呢?

问题就是在平衡化身上,当我们对树结构进行增删修改时,就有可能会面临破环树结构的问题,使其不满足红黑树的定义,那我们该如何解决呢?答案是,我们需要对这些情况通过旋转进行修复,让红黑树保持平衡。

1.左旋

当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。
前提: 当前结点为 h ,它的右子结点为 x
1. x 的左子结点变为 h 的右子结点: h.right=x.left;
2. h 成为 x 的左子结点: x.left=h;
3. h color 属性变为 x color 属性值: x.color=h.color;
4. h color 属性变为 RED h.color=true;

2.右旋

当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋
前提: 当前结点为 h ,它的左子结点为 x
右旋过程:
1. x 的右子结点成为 h 的左子结点: h.left = x.right;
2. h 成为 x 的右子结点: x.right=h;
3. x color 变为 h color 属性值: x.color = h.color;
4. 让h color RED;

 i.向单个2-结点中插入新键

一棵只含有一个键的红黑树只含有一个 2- 结点。插入另一个键后,我们马上就需要将他们旋转。
如果新键小于当前结点的键,我们只需要新增一个红色结点即可,新的红黑树和单个 3- 结点完全等价。

如果新键大于当前结点的键,那么新增的红色结点将会产生一条红色的右链接,此时我们需要通过左旋,把
红色右链接变成左链接,插入操作才算完成。形成的新的红黑树依然和 3- 结点等价,其中含有两个键,一条红
色链接。

 ii.向底部的2-结点插入新键

用和二叉查找树相同的方式向一棵红黑树中插入一个新键,会在树的底部新增一个结点(可以保证有序性),唯一 区别的地方是我们会用红链接将新结点和它的父结点相连。如果它的父结点是一个2- 结点,那么刚才讨论的两种方式仍然适用。

3.颜色反转

当一个结点的左子结点和右子结点的 color 都为 RED 时,也就是出现了临时的 4- 结点,此时只需要把左子结点和右子 结点的颜色变为BLACK ,同时让当前结点的颜色变为 RED 即可。

i.向一棵双键树(即一个3-结点)中插入新键

这种情况有可以分为三种子情况:

 1. 新键大于原树中的两个键

 2. 新键小于原树中的两个键

 3. 新键介于原数中两个键之间

4.根结点的颜色总是黑色

之前我们介绍结点 API 的时候,在结点 Node 对象中 color 属性表示的是父结点指向当前结点的连接的颜色,由于根结点不存在父结点,所以每次插入操作后,我们都需要把根结点的颜色设置为黑色。

i.向树底部的3-结点插入新键

假设在树的底部的一个 3- 结点下加入一个新的结点。前面我们所讲的 3 种情况都会出现。指向新结点的链接可能是 3-结点的右链接(此时我们只需要转换颜色即可),或是左链接 ( 此时我们需要进行右旋转然后再转换 ) ,或是中链 接( 此时需要先左旋转然后再右旋转,最后转换颜色 ) 。颜色转换会使中间结点的颜色变红,相当于将它送入了父结 点。这意味着父结点中继续插入一个新键,我们只需要使用相同的方法解决即可,直到遇到一个2- 结点或者根结点 为止。

 5.代码实现

public class RedBlackTree<Key extends Comparable<Key>,Value> {
	//根节点
	private Node root;
	//记录树中元素的个数
	private int N;
	//红色链接
	private static final boolean Red=true;
	//黑色链接
	private static final boolean Black=false;
//	定义一个内部类结点类
	private class Node{
//		键
		public Key key;
//		值
		private Value value;
//		左子节点
		public Node left;
//		右子节点
		public Node right;
//		链接
		public boolean color;
		public Node(Key key,Value value,Node left,Node right,boolean color) {
			this.key=key;
			this.value=value;
			this.left=left;
			this.right=right;
			this.color=color;
		}
	}
//	判断树中元素是否为空
	public boolean isEmpty() {
		return this.N==0;
	}
	public int size() {
		return this.N;
	}
	private boolean isRed(Node x) {
		//空结点默认是黑色链接
			if(x==null) {
				return false;
			}
			//非空结点需要判断结点color属性的值
			return x.color==Red;
		}
//	左旋转
	private Node rotateLeft(Node h) {
		//找出当前结点h的右子结点
		Node x=h.right;
		//找出右子结点的左子结点,并将其连接到右子结点
		h.right=x.left;
		//让当前结点h成为右子结点的左子结点
		x.left=h;
		//让当前结点h的color变成右子结点的color
		x.color=h.color;
		//让当前结点h的color变为RED
		h.color=Red;
		//返回当前结点的右子结点
		return x;
	}
	private Node rotateRight(Node h) {
		//找出当前结点h的左子结点
		Node x=h.left;
		//找出左子结点的右子结点,并将其连接到左子结点
		h.left=x.right;
		//让当前结点h成为左子结点的右子结点
		x.right=h;
		//让当前结点h的color变成左子结点的color
		x.color=h.color;
		//让当前结点h的color变为RED
		h.color=Red;
		//返回当前结点的左子结点
		return x;
	}
//	颜色反转
	private void flipColors(Node x) {
		//当前结点的color属性值变为RED;
		x.color=Red;
//		//当前结点的左右子结点的color属性值都变为黑色
		x.left.color=Black;
		x.right.color=Black;
	}
//	向整棵树插入(添加)
	public void put(Key key,Value value) {
		//在root整个树上插入key-value
		root=put(root,key,value);
		//让根结点的颜色变为BLACK
		root.color=Black;
	}
//	在指定树中,完成插入操作,并返回添加元素后新的树
	public Node put(Node h,Key key,Value value) {
//		如果h结点为null,则创建一个新结点,并将其返回
		if(h==null) {
//			数目+1;
			N++;
			return new Node(key,value,null,null,Red);
		}
		int cmp=key.compareTo(h.key);
		//比较要插入的键和当前结点的键
		if(cmp<0) {
			//继续寻找左子树插入
			h.left=put(h.left,key,value);
		}else if(cmp>0) {
			//继续寻找右子树插入
			h.right=put(h.right,key,value);
		}else {
			//已经有相同的结点存在,修改节点的值;
			h.value=value;
		}
		//如果当前结点的右链接是红色,左链接是黑色,需要左旋
		if(isRed(h.right)&&isRed(h.left)) {
			h=rotateLeft(h);
		}
		//如果当前结点的左子结点和左子结点的左子结点都是红色链接,则需要右旋
		if(isRed(h.left)&&isRed(h.left.left)) {
			h=rotateRight(h);
		}
		//如果当前结点的左链接和右链接都是红色,需要颜色变换
		if(isRed(h.left)&&isRed(h.right)) {
			flipColors(h);
		}
		return h;
	}
	//根据key,从树中找出对应的值
	public Value get(Key key) {
		return get(root,key);
	}
	//从指定的树x中,查找key对应的值
	private Value get(Node h,Key key) {
		//如果当前结点为空,则没有找到,返回null
		if(h==null) {
			return null;
		}
		//比较当前结点的键和key
		int cmp=key.compareTo(h.key);
		if(cmp<0) {
			//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点
			return get(h.left,key);
		}else if(cmp>0) {
			//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
			return get(h.right,key);
		}else {
			//如果要查询的key等于当前结点的key,则树中返回当前结点的value。
			return h.value;
		}
	} 

}

  活动地址:CSDN21天学习挑战赛

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck&Strive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值