红黑树

一、红黑树是什么

红黑树也是一种二叉搜索树,只是给每个结点增加了一个存储位去存储颜色,通过对树中各结点之间颜色的限制,并且最长路径比最短路径的两倍短,确保其接近平衡。

二、红黑树的性质

1.结点的颜色只有红色或者黑色
2.根结点一定是黑色
3.一个结点为红色,则其的两个孩子结点为黑色(前提是存在孩子结点)
4.每个结点到叶子结点的简单路径上,均包含相同的黑色结点
5.叶子节点(这里指的空的叶子节点)为黑色

三、实现红黑树

3.1红黑树的定义

首先我们需要知道红黑树也是一种二叉搜索树,所以每个结点的存储跟二叉搜索树相似,包括左右指针,父亲指针,颜色以及存储的数据。

enum Color{
	BLACK,
	RED
};
template<class K, class V>
struct RBTreeNode{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
	pair<K, V>_kv;
	RBTreeNode(const pair<K, V>kv = pair<K, V>())
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _color(RED)
		, _kv(kv)
	{}
};

这里我们给定每个节点的初始颜色为红色(插入的节点,除了根结点,初始都应为红色),存储的为键值对数据,是为了后面各项操作的更好的完成。
在创建红黑树时,我们也初始化了它的结构,我们为了让其跟set和map接口的底层结构相同(具体为了方便迭代器的实现),所以我们定义了一个头结点。让其左指针指向红黑树的最左节点,右指针指向红黑树的最右节点。


template<class T, class V>
class RBTree{
public:
	typedef RBTreeNode<T, V> Node;
	RBTree()
		:_header(new Node)
	{
		_header->_left = _header->_right = _header;  //初始化
	}
private:
	Node* _header;  //头节点
}

3.2红黑树的插入

3.2.1插入

在定义中我们知道,红黑树也是一种二叉搜索树,所以插入的方式也和二叉搜索树相似,通过键值对中的Key去进行插入

bool Insert(const pair<T, V>& val){
		//空树
		if (_header->_parent == nullptr){
			Node* cur = new Node(val);
			_header->_left = _header->_right = cur;  //头结点
			cur->_parent = _header;
			_header->_parent = cur;   //头结点的父节点为根节点
			cur->_color = BLACK;  //根结点必须为黑色
			return true;
		}
		//非空(搜索树的插入)
		Node* parent = nullptr;
		Node* cur = _header->_parent;
		while (cur){
			parent = cur;
			if (cur->_kv.first > val.first){
				cur = cur->_left;
			}
			else if (cur->_kv.first < val.first){
				cur = cur->_right;
			}
			else{
				return false;
			}
		}
		cur = new Node(val);
		if (val.first < parent->_kv.first){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}
		cur->_parent = parent;
		return true;
		}

这里只是单单通过键值对中的key值去走了一个二叉搜索树的插入,插入之后,我们需要去调整搜索树的颜色,使它变成一个红黑树。

3.2.2颜色调整

根据红黑树的性质,我们可以知道,红黑树中红色节点不可以连续,所以调整颜色就要从红色连续的节点出发,调整颜色分为一下情况,如图:
情况一:当插入一个节点cur后,它的父节点parent为红色,并且它的叔叔uncle节点为红色.(左边的左边都是红,右边也是红)。
在这里插入图片描述

调整方式:父节点parent和叔叔节点uncle变为黑色,将祖父母节点grandparent变为红色。
情况二:当插入一个节点cur后,它的父节点parent为红色,但是它的叔叔uncle节点为黑色或者不存在.(左边的左边都是红,右边为黑或者不存在)
在这里插入图片描述
调整方式:以祖父母grandparent节点为根节点进行右旋(右旋在AVL树中讲过,不太了解的读者,可以去阅读我的AVL树对应博客),然后将祖父母节点grandparent变为红色,父节点parent变为黑色。
情况三:当插入一个节点cur后,它的父节点parent为红色,但是它的叔叔uncle节点为黑色或者不存在.(左边的右边都是红,右边为黑或者不存在)
在这里插入图片描述
调整方式:以父节点parent为根节点进行左旋旋(左旋在AVL树中讲过,不太了解的读者,也可以去阅读我的AVL树对应博客),然后将当前节点cur和父节点parent的指针调换位置,进行情况而的调整方式即可。
以上我们只说了在左子树上的各种情况,在右子树上类似,读者可以根据一下代码,自行去验证

		//调整颜色
		while (cur != _header->_parent&&cur->_parent->_color == RED){
			Node* parent = cur->_parent;
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent){  //左子树上的各种情况
				Node* unice = grandfather->_right;
				if (unice&&unice->_color == RED){  //情况一
					unice->_color = parent->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;   //继续向上调整
				}
				else{         
					if (parent->_right == cur){   //情况三
						RotateL(parent);
						swap(cur, parent);
					}
					RotateR(grandfather);  //情况二
					parent->_color = BLACK;
					grandfather->_color = RED;
					break;
				}
			}
			else{   //右子树的各种情况
				Node* unice = grandfather->_left;   //叔叔节点在祖父母的左边
				if (unice&&unice->_color == RED){   //叔叔节点存在
					unice->_color = parent->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
				}
				else{   //叔叔节点不存在,或者为黑色
					if (parent->_left == cur){   
						RotateR(parent);
						swap(parent, cur);
					}
					RotateL(grandfather);  //以祖父节点位根左旋
					parent->_color = BLACK;
					grandfather->_color = RED;
					break;
				}
			}
		}
		_header->_parent->_color = BLACK;
		_header->_left = leftMost();   //红黑树的最左节点
		_header->_right = rightMost();  //红黑树的最右节点	

在插入结束后,我们需要去让根节点的颜色变为黑色,并且去调整头节点的指向。
注意:通过三种情况的图片我们呢可以看出,情况一相对于情况二三,最终调整后它的最上边的节点为红色,所以有可能会造成上边红黑树遭到破坏,所以还需要向上继续调整,直到调整到根节点,或者不存在红色连续的节点。

3.3红黑树的迭代器

红黑树每个节点属于物理不连续的,并且每个节点包括很多的区域,所以不能通过原生的迭代器去操作红黑树,需要去通过节点去的内部区域去实现,具体如下:
红黑树迭代器的基本结构:

struct RBTreeIterator{
	typedef RBTreeNode<K, V> Node;
	typedef RBTreeIterator<K, V> Self;
	Node* _node;
		RBTreeIterator(Node* node)
		:_node(node)
	{}

通过操作节点去实现*、->、!=、++、–等操作

	pair<K, V>& operator*(){
		return _node->_kv;
	}
	pair<K, V>* operator->(){
		return &_node->_kv;
	}
	bool operator!=(const Self& it){
		return _node != it._node;
	}

*则是得到数据,->则是得到数据的相应地址,!=则是通过节点的比较就看一判断
最重要的就是++,–操作,首先我们说++,这是一个二叉搜索树,所以在得迭代器++过程中,我们访问的也是一个"有序"的数据,也就相当于中序遍历(左根右),所以在++时,我们首先需要去考虑目前节点是否有右子树,如果存在右子树,则需要去访问右子树的最左节点,如果不存在,则需要去判断该节点是否为它父节点的右节点,如果是,则说明在访问当前节点时,它的父节点已经被访问了,这是就需要调整当前节点的指向,使其指向父节点,让其父节点向上移动,最终让节点位置移动到向上移动后的父节点,如果目前节点不是它父节点的右节点,则访问父节点即可。这里需要注意的是,如果该红黑树没有右子树,则它的头结点的右指针就会指向根,这样会造成节点向上移动的死循环,所以我们需要去给定限制,使其在根节点是结束循环。
下面我们通过图片说明:
在这里插入图片描述
具体代码如下:

	Self& operator++(){
		if (_node->_right){  //该节点右子树存在,则访问右子树的最左节点
			_node = _node->_right;
			while (_node->_left){
				_node = _node->_left;
			}
		}
		else{     //不存在
			Node* parent = _node->_parent;
			if (parent->_parent != _node){  //限制条件,访问到根节点结束
				while (parent->_right == _node){
					_node = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return *this;
	}
	Self& operator--(){
		if (_node->_left){  //该节点左子树存在,则访问右子树的最右节点
			_node = _node->_left;
			while (_node->_right){
				_node = _node->_right;
			}
		}
		else{     //不存在
			Node* parent = _node->_parent;
			if (parent->_parent != _node){
				while (parent->_left == _node){
					_node = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return *this;
	}

红黑树的迭代器实现完成后,最终在红黑树中如下代码实现:

	iterator begin(){
		return iterator(_header->_left);
	}
	iterator end(){
		return iterator(_header);
	}
	iterator rbegin(){
		return iterator(_header->_right);
	}
	iterator rend(){
		return iterator(_header);
	}

3.4红黑树的其他操作

3.4.1简单操作函数

这里我们去实现上边头结点的左右指针指向位置的函数
左指针指向红黑树的最左节点:

	Node* leftMost(){
		Node* cur = _header->_parent;
		while (cur->_left){
			cur = cur->_left;
		}
		return cur;
	}

右指针指向红黑树的最右节点:

	Node* rightMost(){
		Node* cur = _header->_parent;
		while (cur->_right){
			cur = cur->_right;
		}
		return cur;
	}

还有上边左旋和右旋,由于和AVL树的节点结构不同,所以在这里再写一次:

	//右旋
	void RotateR(Node* root){
		Node* Lcur = root->_left;
		Node* LRcur = Lcur->_right;
		root->_left = LRcur;
		if (LRcur)
			LRcur->_parent = root;
		Lcur->_right = root;
		if (root == _header->_parent){    //这里判断是否为头结点的父节点
			_header->_parent = Lcur;
			Lcur->_parent = _header;
		}
		else{
			Node* grandparent = root->_parent;
			Lcur->_parent = grandparent;
			if (grandparent->_left == root)
				grandparent->_left = Lcur;
			else
				grandparent->_right = Lcur;
		}
		root->_parent = Lcur;
	}
	//左旋
	void RotateL(Node* root){
		Node* Rcur = root->_right;
		Node* RLcur = Rcur->_left;
		root->_right = RLcur;
		if (RLcur)
			RLcur->_parent = root;
		Rcur->_left = root;
		if (root == _header->_parent){
			_header->_parent = Rcur;
			Rcur->_parent = _header;
		}
		else{
			Node* grandparent = root->_parent;
			Rcur->_parent = grandparent;
			if (grandparent->_left == root)
				grandparent->_left = Rcur;
			else
				grandparent->_right = Rcur;
		}
		root->_parent = Rcur;
	}

3.4.2红黑树的判断

根据红黑树的性质以及定义,要判断一棵树是否为红黑树,首先需要判断它是否为二叉搜索树,所以先通过中序遍历验证:

	void inorder(){
		Node *_root = _header->_parent;
		if (_root){
			_inorder(_root);
		}
	}
	void _inorder(Node* root){
		if (root){
			_inorder(root->_left);
			cout << root->_kv.first << "-" << root->_kv.second << "-" << root->_color << endl;
			_inorder(root->_right);
		}
	}

接着就需要去判断是否为红黑树了
1.根节点为黑色
2.不能出现连续红色节点
3.每个结点到叶子结点的简单路径上,均包含相同的黑色结点
代码如下:

	bool isRBTree(){
		Node* root = _header->_parent;
		if (root == nullptr){
			return true;
		}
		if (root->_color == RED){     //1
			cout << "root not black" << endl;
			return false;
		}
		//计算一条路径黑色节点个数
		int black_num = 0;
		Node* cur = root;
		while (cur){
			if (cur->_color == BLACK){
				black_num++;
			}
			cur = cur->_left;
		}
		int k = 0;
		return _isRBTree(root, k, black_num);
	}
	bool _isRBTree(Node* root, int k, int black_num){
		if (root == nullptr){
			if (k == black_num){     //3
				return true;
			}
			cout << "black not enough" << endl;
			return false;
		}
		if (root->_color == BLACK){
			k++;
		}
		if (root->_parent != nullptr&&root->_color == RED&&root->_parent->_color == RED){    //2
			cout << "red continuous" << endl;
			return false;
		}
		return _isRBTree(root->_left, k, black_num) && _isRBTree(root->_right, k, black_num);
	}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值