平衡树之红黑树

目录

红黑树概念

红黑树的性质

红黑树的插入:

 红黑树的验证:

与AVL树的比较

代码:


红黑树概念

红黑树,是一种二叉搜索树,但在每个节点上增加一个存储位置表示节点的颜色,可以是red或black。通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的,而AVL树是高度平衡的。C++STL中,很多部分(包括set,multiset,map,multimap)的底层都是红黑树。对于任意一颗红黑树,它可以在O(log2 N)时间内做查找,插入和删除,这里的N是树中元素的数目。

红黑树的性质

红黑树是每个节点都带有颜色属性的二叉查找树,颜色是红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树还增加了如下的额外要求:

  1. 每个节点不是红色就是黑色
  2. 根节点是黑色的
  3. 每个叶子节点都是黑色的(此处的叶子节点指的是空节点NIL)
  4. 如果一个节点是红色的,则它的两个孩子节点是黑色的
  5. 对于每个节点,从该节点到其后代叶子节点的简单路径上,均包含相同数目的黑色节点。

如图: 

 在红黑树的概念中提到红黑树是接近平衡的,因为红黑树确保没有一条路径会比其他路径长处两倍。为什么?

由于每个节点非红即黑(性质1),加上不存在两个连续的红节点(性质4),那么红黑树的最长路径一定是红黑交替的,而最短路径一定全部都是黑节点。而任意节点其所有后代叶节点的简单路径上,均包含相同数目的黑色节点,则说明最长路径和最短路径中的黑色节点数目一定相等。最后加上根节点为黑(性质2)和叶子节点为黑(性质3),那么一定可以得出一个结论:最长路径  <= 2 * 最短路径

红黑树的插入:

当我们在对红黑树进行插入操作时,对树做了修改,那么可能会违背红黑树的性质。为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树中某些节点的颜色及指针结构,以达到对红黑树进行插入、删除节点等操作时,红黑树依旧能保持它特有的性质。

红黑树的插入分为以下情况,在了解之前,我们还要必须知道插入的节点是红色的,如果插入节点为黑色,总是会破环性质5,而插入红节点不一定会破坏性质5。因为在插入之前,该树已经是红黑树了,插入了黑节点肯定会影响该节点到根节点的这条路径。 

我们将插入的节点标记为cur,父节点标记为p,祖父节点为g,叔叔节点为u。

①该树为空树,直接插入根节点的位置,再把节点颜色改为黑色即可。

②插入节点 cur的父节点p为黑色,不违反任何性质,无需做任何修改。

cur为红,p为红(祖父节点一定存在,且为黑,下边同理)u也为红,这里不论p是g的左孩子,还是右孩子;不论cur是p的左孩子,还是右孩子。

为什么在这个情况下,祖父节点一定存在?

因为p节点为红,假设祖父节点不存在,那么p将称为整个树的根,违反了性质2(根节点为黑色)。

这种情况下,为了让以g为跟的子树不存在连续的红色节点,并且不让增加路径上的黑色节点,我们让g的颜色变红,让u和p的颜色变黑,各路径上黑色节点数量没变,依次达到红黑树性质。

因为p和u的颜色变黑,对子树没有影响,但是g的颜色变红,可能存在影响,需要继续向上判断:

  1. 如果g为该树的根,那么最后需要将g的颜色变黑
  2. 如果g是子树,如果g的父节点是黑色节点则不用调整,如果是红色节点则需要根据情况来做调整。

 ④cur为红,u为黑(存在为黑或者不存在),p为g的左孩子,cur为p的左孩子(或者p为g的右孩子,cur为p的左孩子),就是同向的。u的情况有两种:

1、如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。

2、如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色。

在此情况下,需要将p、g变色,p、g变换即左单旋(或者右单旋) 

代码:(这里的parent为g)

//右单旋
	void RotateR(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right; //subL的右边可能为nullptr
		Node* pparent = parent->_parent;
		//将subLR连接到parent的左边
		parent->_left = subLR;
		if (subLR != nullptr) {
			subLR->_parent = parent;
		}
		//将parent连接到subL的右侧
		subL->_right = parent;
		parent->_parent = subL;
		//将subL和pparent连接起来
		//pparent为nullptr,subL要成为新的根
		if (pparent == nullptr) {
			subL->_parent = pparent;
			_root = subL;
		}
		else {
			if (pparent->_left == parent) {
				pparent->_left = subL;
			}
			else {
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	//左单旋
	void RotateL(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left; //subR的左边可能为nullptr
		Node* pparent = parent->_parent;
		parent->_right = subRL;
		if (subRL != nullptr) {
			subRL->_parent = parent;
		}

		subR->_left = parent;
		parent->_parent = subR;
		if (pparent == nullptr) {
			subR->_parent = pparent;
			_root = subR;
		}
		else {
			if (pparent->_right == parent) {
				pparent->_right = subR;
			}
			else {
				pparent->_left = subR;
			}
			subR->_parent = pparent;
		}
	}

⑤cur为红,p为红,u为黑(存在为黑或者不存在),p为g的左孩子,cur为p的右孩子(或者p为g的右孩子,cur为p的左孩子;反正两者相反)

此处为右左单旋:

 红黑树的验证:

红黑树的验证分为两步:

  1. 检测是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测是否满足红黑树的性质

bool IsRBTree() {
		//空树
		if (_root == nullptr) {
			return true;
		}
		//根节点为黑色
		if (_root->_cor == RED) {
			cout << "根节点为红色" << endl;
			return false;
		}
		//黑色节点数量相同,先走一条得到基准值
		int blacknum = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_cor == BLACK) {
				blacknum++;
			}
			cur = cur->_left;
		}
		//检查子树
		return _IsRBTree(_root, blacknum, 0);
	}
	bool _IsRBTree(Node* root, int blacknum, int count) {
		//递归到空节点
		if (root == nullptr) {
			if (blacknum == count) {
				return true;
			} 
			else{
				cout << "各路径黑色节点个数不同" << endl;
				return false;
			}
		}
		//子节点为红,则检查父节点是否为红
		if (root->_cor == RED && root->_parent->_cor == RED) {
			cout << "存在连续红节点" << endl;
			return false;
		}
		//计数黑节点
		if (root->_cor == BLACK) count++;
		//递归左右子树
		return _IsRBTree(root->_left, blacknum, count) && _IsRBTree(root->_right, blacknum, count);
	}

与AVL树的比较

  1. 都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN)
  2. 红黑树不追求绝对的平衡,相对而言,降低了插入和旋转的次数
  3. 所以再经常进行增删的结构中性能更好,而且实现比AVL树简单。

代码:

#pragma once

#include<iostream>
using namespace std;

enum Color {
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode {
	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	pair<K, V> _kv;
	Color _cor;

	RBTreeNode(const pair<K, V>& kv) 
	:_parent(nullptr)
	,_left(nullptr)
	,_right(nullptr)
	,_kv(kv)
	,_cor(RED){}
};

template<class K, class V>
class RBTree {
public:
	typedef RBTreeNode<K, V> Node;
	RBTree()
	:_root(nullptr){}
	bool Insert(const pair<K, V>& kv) {
		//_root为nullptr,说明是插入的第一个值
		if (_root == nullptr) {
			_root = new Node(kv);
			_root->_cor = BLACK;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		//查找插入位置
		while (cur) {
			if (kv.first < cur->_kv.first) {
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first) {
				parent = cur;
				cur = cur->_right;
			}
			else {
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first) {
			parent->_right = cur;
			cur->_parent = parent;
		}
		else {
			parent->_left = cur;
			cur->_parent = parent;
		}
		//控制平衡
		while (parent != nullptr && parent->_cor == RED) {
			//不需要判断greadfather是否为nullptr,因为parent如果是红色,不可能是根
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left) {
				Node* uncle = grandfather->_right;
				//情况一:uncle存在且为红,不需要旋转处理
				if (uncle && uncle->_cor == RED) {
					//变色 + 向上继续调整
					parent->_cor = BLACK;
					uncle->_cor = BLACK;
					grandfather->_cor = RED;
					cur = grandfather;
					parent = grandfather->_parent;
				}
				//情况二:uncle存在且为黑/不存在
				else {
					//cur为parent的左孩子,需要进行右单旋
					if (cur == parent->_left) {
						RotateR(grandfather);
						parent->_cor = BLACK;
						grandfather->_cor = RED;
					}
					//cur为parent的右孩子,需要左右双旋
					else {
						RotateLR(grandfather);
						cur->_cor = BLACK;
						grandfather->_cor = RED;
					}
					//不用再往上更新
					break;
				}
			}
			//parent为grandfather的右孩子
			else {
				Node* uncle = grandfather->_left;
				//情况一:uncle存在且为红,不需要旋转处理
				if (uncle != nullptr && uncle->_cor == RED) {
					//变色 + 向上调整
					parent->_cor = uncle->_cor = BLACK;
					grandfather->_cor = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二:uncle存在且为黑/不存在
				else {
					//cur为parent的右孩子,需要进行左单旋
					if (cur == parent->_right) {
						RotateL(grandfather);
						parent->_cor = BLACK;
						grandfather->_cor = RED;
					}
					//为左孩子,进行右左双旋
					else {
						RotateRL(grandfather);
						cur->_cor = BLACK;
						grandfather->_cor = RED;
					}
					break;
				}
			}
			//保证根节点为黑
			_root->_cor = BLACK;
			return true;
		}
	}
	bool IsRBTree() {
		//空树
		if (_root == nullptr) {
			return true;
		}
		//根节点为黑色
		if (_root->_cor == RED) {
			cout << "根节点为红色" << endl;
			return false;
		}
		//黑色节点数量相同,先走一条得到基准值
		int blacknum = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_cor == BLACK) {
				blacknum++;
			}
			cur = cur->_left;
		}
		//检查子树
		return _IsRBTree(_root, blacknum, 0);
	}
	bool _IsRBTree(Node* root, int blacknum, int count) {
		//递归到空节点
		if (root == nullptr) {
			if (blacknum == count) {
				return true;
			} 
			else{
				cout << "各路径黑色节点个数不同" << endl;
				return false;
			}
		}
		//子节点为红,则检查父节点是否为红
		if (root->_cor == RED && root->_parent->_cor == RED) {
			cout << "存在连续红节点" << endl;
			return false;
		}
		//计数黑节点
		if (root->_cor == BLACK) count++;
		//递归左右子树
		return _IsRBTree(root->_left, blacknum, count) && _IsRBTree(root->_right, blacknum, count);
	}
private:
	Node* _root;
	//右单旋
	void RotateR(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right; //subL的右边可能为nullptr
		Node* pparent = parent->_parent;
		//将subLR连接到parent的左边
		parent->_left = subLR;
		if (subLR != nullptr) {
			subLR->_parent = parent;
		}
		//将parent连接到subL的右侧
		subL->_right = parent;
		parent->_parent = subL;
		//将subL和pparent连接起来
		//pparent为nullptr,subL要成为新的根
		if (pparent == nullptr) {
			subL->_parent = pparent;
			_root = subL;
		}
		else {
			if (pparent->_left == parent) {
				pparent->_left = subL;
			}
			else {
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	//左单旋
	void RotateL(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left; //subR的左边可能为nullptr
		Node* pparent = parent->_parent;
		parent->_right = subRL;
		if (subRL != nullptr) {
			subRL->_parent = parent;
		}

		subR->_left = parent;
		parent->_parent = subR;
		if (pparent == nullptr) {
			subR->_parent = pparent;
			_root = subR;
		}
		else {
			if (pparent->_right == parent) {
				pparent->_right = subR;
			}
			else {
				pparent->_left = subR;
			}
			subR->_parent = pparent;
		}
	}
	//右左双旋
	void RotateRL(Node* parent) {
		RotateR(parent->_right);
		RotateL(parent);
	}
	//左右双旋
	void RotateLR(Node* parent) {
		RotateR(parent->_left);
		RotateL(parent);
	}
};


int main() {
	RBTree<int, int> t;
	t.Insert(make_pair(1, 1));
	t.Insert(make_pair(2, 2));
	t.Insert(make_pair(3, 3));
	if (t.IsRBTree()) {
		cout << "T" << endl;
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值