数据结构树之红黑树

这里主要解决关于大名鼎鼎的红黑树的下面的问题
(1)理解什么是红黑树
(2)实现红黑树操作(添加和删除)
(3)性能测试

一、理解什么是红黑树
1、理解什么是2-3树
2、红黑树和2-3树的等价性

1、首先,理解红黑树之前,我们先来解决第一个问题,什么是2-3树?
在这里插入图片描述
这样的一颗树就是2-3树
(1)每个节点可以有1个或者2个元素
(2)满足二叉搜索树的性质
(3)是一种绝对平衡树

2-3树是怎么维持绝对平衡的过程(这里只讲插入过程,删除过程类似)
根据二叉搜索树的特点,进行数据的判断,找到插入的位置,假设在根节点40中插入30,二叉搜索树的插入如下
在这里插入图片描述

但是这样会破坏2-3树的平衡性,所以应该这样形成一个节点
在这里插入图片描述

如果再插入一个25呢
在这里插入图片描述
这样破坏了绝对平衡性,所以应该这样
在这里插入图片描述
但是这样一个节点就有3个元素了,所以这个时候就会分解
在这里插入图片描述
这样即具有二叉搜索树的性质,又维持了平衡性
如果再插入26 22呢
插入26如下
在这里插入图片描述
插入22
在这里插入图片描述
很明显,需要进行分解,分解后如下
在这里插入图片描述
但是这样就不在满足绝对的平衡性了,所以需要合并
在这里插入图片描述
这样不断在插入元素,然后根据情况进行分解合并可以使2-3树是一个绝对平衡的数

2、红黑树和2-3树的等价性
可以这么说,红黑树是2-3树的一种二叉表示的数,实现的原理就是如果2-3数中一个节点具有2个元素,那在红黑树的表现如下
在这里插入图片描述
也就是红色的节点表示与其父节点在2-3树中是一个节点,这里并且要保证红色节点的左倾性,如下
在这里插入图片描述
这个时候需要进行左旋转,变成这样
在这里插入图片描述

现在我把开始的那张2-3树的图转换为红黑树,如下
在这里插入图片描述
上的数看起来很奇怪,但是如果认真看这个就是一棵二叉树,不过我为了表示红黑树和2-3树的等价性故意画成这样,所以说红黑树等价于2-3树,那么红黑树就具有如下的特点
(1)红黑树不是很平衡的数,准确的说,红黑树不能保证这个二叉树是平衡二叉树,但是红黑树是“黑平衡”的二叉树,也就是每一个叶子节点到根的路径经过的黑色节点数目是一样的(这个由与2-3树等价可以知道,因为2-3树是绝对平衡的)
(2)由于红黑树不是一棵树不是一棵平衡二叉树,那么如果只论查询数据的效率而已,红黑树并不比AVL树的性能更优。红黑树存在的意义在于,他在统计意义上一般是最优的(添加,删除,查询)

二、实现红黑树的操作
1、添加
(1)在一个左右子树都为空的节点上添加
添加在左边:在这里插入图片描述
添加在右边:保证红色节点的左倾性,需要左旋转
在这里插入图片描述
(2)在左子节点的左边添加:右旋转后颜色翻转(1-3-4-5)
(3)在左子节点的右边添加:左旋转后右旋转,最后颜色翻转(1-2-3-4-5)
(4)左节点为红色节点,然后在右节点添加(1-4-5)
在这里插入图片描述
由上得知,上面的步骤无非只有3个子步骤:颜色翻转,左旋转,右旋转
代码如下

	
	/**
	 * 颜色翻转,这里不舍临界条件,比如node是否为空,node左右子树是否为空是因为使用这个方法前就已经知道node和node的左右子树不为空
	 * @param node
	 */
	private void turnColor(Node<K, V> node)
	{
		node.color=Color.Red;
		node.left.color=Color.Black;
		node.right.color=Color.Black;
	}
	
	/**
	 * 左旋转
	 * @return
	 */
	private Node<K, V> leftRotate(Node<K, V> node)
	{
		//正常的旋转过程
		Node<K, V> node1=node.right;
		Node<K, V> node2=node1.left;
		
		node1.left=node;
		node.right=node2;
		
		//维护颜色
		node1.color=node.color;
		node.color=Color.Red;
		return node1;
	}
	
	/**
	 * 右旋转
	 * @param node
	 * @return
	 */
	private Node<K, V> rightRotate(Node<K, V> node)
	{
		//正常的选择过程
		Node<K, V> node1=node.left;
		Node<K, V> node2=node1.right;
		
		node1.right=node;
		node.left=node2;
		
		//维护颜色
		node1.color=node.color;
		node.color=Color.Red;
		return null;
	}
	

添加操作:

	/**
	 * 向AVL树中插入新的节点,插入之后进行平衡使这个数一直是平衡二叉树
	 * @param node
	 */
	public void add(K key)
	{
		add(key, null);
	}
	
	public void add(K key, V value)
	{
		//使用递归来实现
		root=add(root, key, value);
	}
	
	private Node<K, V> add(Node<K, V> node, K key, V value)
	{
		//如果这个节点为空,那么新建一个节点返回给它的父节点
		if(node==null)
		{
			size++;
			return new Node<K, V>(key, value);
		}
		//如果key小于目前节点,进入其左子树
		if(key.compareTo(node.key)<0)
			node.left=add(node.left, key, value);
		//如果key大于目前节点,进入其右子树
		else if(key.compareTo(node.key)>0)
			node.right=add(node.right, key, value);
		else
			node.value=value;
		
		//颜色维护过程
		//如果node的右节点是红色,同时左节点不是红色,那么发生左旋转
		if(isRed(node.right)&&!isRed(node.left))
			node=leftRotate(node);
		
		//如果node的左节点是红色,左节点的左节点也是红色,那么右旋转
		if(isRed(node.left)&&isRed(node.left.left))
			node=rightRotate(node);
		
		//如果左节点是红色,右节点也是红色,那么颜色翻转
		if(isRed(node.left)&&isRed(node.right))
			turnColor(node);
		
		return node;
	}
	

当然,需要在Node中添加一个项纪录节点颜色

	//枚举颜色
	enum Color{
		Red,Black;
	}
	//新节点开始都是叶子节点,而叶子节点一开始都是红色,所以默认是红色
	private Color color=Color.Red;
		

2、删除(由于删除操作和添加操作时基本一样的,所以这里就不再说明)

	
	/**
	 * 删除节点
	 * @param key
	 */
	public void remove(K key)
	{
		root=remove(root,key);
	}
	private Node<K, V> remove(Node<K, V> node,K key)
	{
		//如果node为空,表示找不到
		if(node==null)
			return null;
		
		Node<K, V> retNode;
		
		//key小于node的key,那么在其左子树上删除
		if(key.compareTo(node.key)<0)
		{
			node.left=remove(node.left,key);
			retNode=node;
		}
		//key大于node的key,那么在其右子树上删除
		else if(key.compareTo(node.key)>0)
		{
			node.right=remove(node.right, key);
			retNode=node;
		}
		//key等于node的key,那么删除node,然后维护这个新树
		else
		{
			size--;
			//表示删除node
			//node左子树如果是null,那么返回其右子树即可
			if(node.left==null)
				retNode=node.right;
			//node左子树不为null,右子树是null,那么返回其左子树即可
			else if(node.right==null)
				retNode=node.left;
			//node的左右子树都不为null,那么返回node节点中序遍历的后继节点,同时维护这棵新树
			else {
				//node的右子节点
				Node<K, V> nodeMidNext=node.right;
				//如果nodeRight的左子树为空,那nodeRight就是node节点中序遍历的后继节点
				if(nodeMidNext.left==null)
				{
					//维护新数
					nodeMidNext.left=node.left;
					retNode=nodeMidNext;
				}
				//如果其左子树不为null,那么nodeRight的最左子树就是node节点中序遍历的后继节点
				else
				{
					//这个后继节点的父节点
					Node<K, V> nodeMidNextParent=nodeMidNext;
					nodeMidNext=nodeMidNext.left;
					//找到这个后继节点
					while(nodeMidNext.left!=null)
					{
						nodeMidNextParent=nodeMidNext;
						nodeMidNext=nodeMidNext.left;
					}
					//维护新数
					nodeMidNextParent.left=nodeMidNext.right;
					nodeMidNext.left=node.left;
					nodeMidNext.right=node.right;
					retNode=nodeMidNext;
				}
			}
		}
		
		//颜色维护过程
		//如果node的右节点是红色,同时左节点不是红色,那么发生左旋转
		if(isRed(retNode.right)&&!isRed(retNode.left))
			retNode=leftRotate(retNode);
		
		//如果node的左节点是红色,左节点的左节点也是红色,那么右旋转
		if(isRed(retNode.left)&&isRed(retNode.left.left))
			node=rightRotate(retNode);
		
		//如果左节点是红色,右节点也是红色,那么颜色翻转
		if(isRed(retNode.left)&&isRed(retNode.right))
			turnColor(retNode);
		
		return retNode;
		
	}

三、性能测试
测试代码:

	public static void main(String[] args) {
		BST<Integer, Object> bst=new BST<>();
		AVLTree<Integer, Object> avl=new AVLTree<>();
		RBTree<Integer, Object> rbt=new RBTree<>();
		//BST
		long startTime = System.nanoTime();
		for(int i=1;i<=10000;i++)
		{
			bst.add(i);
		}
		for(int i=1;i<=10000;i++)
		{
			bst.get(i);
		}
		long endTime = System.nanoTime();
		System.out.println("BST: "+(endTime-startTime)/(100000000-0.0)+"s");
		
		//AVL
		startTime = System.nanoTime();
		for(int i=1;i<=10000;i++)
		{
			avl.add(i);
		}
		for(int i=1;i<=10000;i++)
		{
			avl.get(i);
		}
		endTime = System.nanoTime();
		System.out.println("AVL: "+(endTime-startTime)/(100000000-0.0)+"s");
		
		//RBTree
		startTime = System.nanoTime();
		for(int i=1;i<=10000;i++)
		{
			rbt.add(i);
		}
		for(int i=1;i<=10000;i++)
		{
			rbt.get(i);
		}
		endTime = System.nanoTime();
		System.out.println("RBTree: "+(endTime-startTime)/(100000000-0.0)+"s");
	}

测试结果:
在这里插入图片描述

上面并不是说明红黑树在任何情况下效率都比AVL树的效率高,而是说明在整体的统计意义上红黑树的效率好一点(毕竟红黑树不是平衡二叉树,如果单论查找效率要比AVL树差一点)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值