C++_图解手撕红黑树的插入-查找-判断_KeyValue模型(三叉链)

Posted dodamce

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++_图解手撕红黑树的插入-查找-判断_KeyValue模型(三叉链)相关的知识,希望对你有一定的参考价值。

1.红黑树概念

先看AVL树:
AVL树为高度平衡二叉搜索树
AVL树插入的实现
再看红黑树:
红黑树也是二叉搜索树。但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的

红黑树的性质

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

保证上述性质后,红黑树就确保没有一条路径会比其他路径长出2倍。
原因:假设一条路径上黑色节点为3个。这颗树最短路径为全黑色3,最长路径为黑红交替6。最长路径正好为最短路径的二倍,正好符合红黑树结构。

2.红黑树KV模型

基本结构

#pragma once

#include<iostream>
using namespace std;

enum Color

	RED = 0, BLACK,
;

template<class Key, class Value>
struct RBTreeNode

	RBTreeNode<Key, Value>* _left;
	RBTreeNode<Key, Value>* _right;
	RBTreeNode<Key, Value>* _parent;
	pair<Key, Value> _kv;
	Color col;
	RBTreeNode(const pair<Key, Value>& val)
		:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(val), col(RED)//默认节点颜色
	
;


template<class Key,class Value>
class RBTree

	typedef RBTreeNode<Key, Value> Node;
public:
	RBTree() :_root(nullptr) 

	pair<Node*, bool>Insert(const pair<Key, Value>val);//红黑树的插入
private:
	Node* _root;
;

3.红黑树的插入

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点。
  2. 修改节点颜色,保持红黑树特性

情况一: 红黑树为空,此时插入时创建一个节点,并将节点的颜色改为黑色。

pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入

	if (_root == nullptr)
	
		_root = new Node(val);
		_root->col = BLACK;
		return make_pair(_root, true);
	

情况二:
1.红黑树不为空,首先遍历红黑树,找到要插入的位置。同时找是否有重复的节点,如果发现有重复的节点插入失败,返回这个位置的指针和false。为了实现三叉链,还要一个parent指针指向cur的上一个节点

//二叉搜索树插入
Node* cur = _root;
Node* parent = nullptr;
while (cur != nullptr)

	if (cur->_kv.first < val.first)
	
		parent = cur;
		cur = cur->_right;
	
	else if (cur->_kv.first > val.first)
	
		parent = cur;
		cur = cur->_left;
	
	else//键值重复,插入失败
	
		return make_pair(cur, false);
	

2.上面这个函数结束后,此时cur就指向nullptr,parent指向要连接的上一个位置

cur创建一个新的红色节点,根据二叉搜索树特性,还要判断cur节点和parent节点值的大小,如果cur节点值小于parent插入到parent节点的左边,否则插入到parent节点的右边。

cur = new Node(val);
cur->col = RED;
if (parent->_kv.first > val.first)

	parent->_left = cur;

else

	parent->_right = cur;

cur->_parent = parent;

3.到这里,节点的插入就算完成了,我们还要调整红黑树的节点颜色,让其符合红黑树的规则。在这之前记录插入节点的位置,方便返回pair值

其次是调整红黑树颜色
为了方便描述,记录四个节点的名称cur,parent,uncle,grandparent节点,这四者位置入下图所示,简称c p u g

如果插入的parent是黑色,不需要调整树的颜色,插入完成。

插入的parent是红色,两个红色连续。需要处理

①p是g左子树 cur为红,p为红,g为黑,u存在且为红


根据上图可以写出代码

while (parent != nullptr && parent->col == RED)//停止条件看上图分析

	Node* GradParent = parent->_parent;//记录gradparent节点
	if (parent == GradParent->_left)//关键看Uncle节点的颜色
	
		Node* Uncle = GradParent->_right;
		//情况1:Uncle存在且为红
		if (Uncle && Uncle->col == RED)
		
			parent->col = Uncle->col = BLACK;
			GradParent->col = RED;
			cur = GradParent;//循环向上判断
			parent = cur->_parent;
		
	
	else//parent=GradParent->_right
	
		......
	

_root->col = BLACK;//将根的颜色变为黑色,防止上面的过程将根节点变为红色
return make_pair(End, true);

②p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为一条直线)_单旋

首先:出现这种情况时,cur一定不是新插入的节点。一定是在情况一向上调整颜色时出现的
理由:

处理方式:

根据上面的分析,我们可以写出如下代码

pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入

	if (_root == nullptr)
	
		_root = new Node(val);
		_root->col = BLACK;
		return make_pair(_root, true);
	
	//二叉搜索树插入
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur != nullptr)
	
		if (cur->_kv.first < val.first)
		
			parent = cur;
			cur = cur->_right;
		
		else if (cur->_kv.first > val.first)
		
			parent = cur;
			cur = cur->_left;
		
		else//键值重复,插入失败
		
			return make_pair(cur, false);
		
	
	cur = new Node(val);
	cur->col = RED;
	if (parent->_kv.first > val.first)
	
		parent->_left = cur;
	
	else
	
		parent->_right = cur;
	
	cur->_parent = parent;
	Node* End = cur;//记录插入位置的节点
	//控制路径
	while (parent != nullptr && parent->col == RED)
	
		Node* GradParent = parent->_parent;
		if (parent == GradParent->_left)//关键看Uncle节点的颜色
		
			Node* Uncle = GradParent->_right;
			//情况1:Uncle存在且为红
			if (Uncle && Uncle->col == RED)
			
				parent->col = Uncle->col = BLACK;
				GradParent->col = RED;
				cur = GradParent;
				parent = cur->_parent;
			
			else//Uncle不存在或Uncle存在为黑色
			
				if (cur == parent->_left)//右高,进行右旋
				
					_Single_Right(GradParent);
					GradParent->col = RED;
					parent->col = BLACK;
				
				break;//旋转后黑色节点个数不变,直接跳出循环
			
		else//parent=GradParent->_right
		
			......
		
	
	_root->col = BLACK;//将根的颜色变为黑色,防止情况一最后到根上跳出根变成红色
	return make_pair(End, true);

右单旋代码与AVL树的右旋转类似,根据下图旋转图写出代码即可

void _Single_Right(Node* parent)//右单旋根据图把对应关系连接起来

	//记录要移动的节点
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;

	//连接
	parent->_left = SubLR;
	if (SubL->_right != nullptr)//修改父指针
	
		SubLR->_parent = parent;
	
	//连接
	SubL->_right = parent;
	Node* GradParent = parent->_parent;//记录这个节点的父节点,为了修改根节点
	parent->_parent = SubL;//修改父指针
	//调整根节点
	if (parent == _root)//要旋转的节点为根节点
	
		_root = SubL;
		SubL->_parent = GradParent;
	
	else//要旋转的节点是子树,修改GradParent指针
	
		if (GradParent->_left == parent)
		
			GradParent->_left = SubL;
		
		else
		
			GradParent->_right = SubL;
		
		SubL->_parent = GradParent;
	

③p是g的左子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋


处理方式:

根据上图写出代码

pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入

	if (_root == nullptr)
	
		_root = new Node(val);
		_root->col = BLACK;
		return make_pair(_root, true);
	
	//二叉搜索树插入
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur != nullptr)
	
		if (cur->_kv.first < val.first)
		
			parent = cur;
			cur = cur->_right;
		
		else if (cur->_kv.first > val.first)
		
			parent = cur;
			cur = cur->_left;
		
		else//键值重复,插入失败
		
			return make_pair(cur, false);
		
	
	cur = new Node(val);
	cur->col = RED;
	if (parent->_kv.first > val.first)
	
		parent->_left = cur;
	
	else
	
		parent->_right = cur;
	
	cur->_parent = parent;
	Node* End = cur;//记录插入位置的节点
	//控制路径
	while (parent != nullptr && parent->col == RED)
	
		Node* GradParent = parent->_parent;
		if (parent == GradParent->_left)//关键看Uncle节点的颜色
		
			Node* Uncle = GradParent->_right;
			//情况1:Uncle存在且为红
			if (Uncle && Uncle->col == RED)
			
				parent->col = Uncle->col = BLACK;
				GradParent->col = RED;
				cur = GradParent;
				parent = cur->_parent;
			
			else//Uncle不存在或Uncle存在为黑色
			
				if (cur == parent->_left)//右高,进行右旋
				
					_Single_Right(GradParent);
					GradParent->col = RED;
					parent->col = BLACK;
				
				else//折线形状,左右双旋
				
					_Single_Left(parent);
					_Single_Right(GradParent);
					cur->col = BLACK;
					parent->col = GradParent->col = RED;
				
				break;//旋转后黑色节点个数不变,直接跳出循环
			
		else//parent=GradParent->_right
		
			......
		
	
	_root->col = BLACK;//将根的颜色变为黑色,防止上面的过程将根节点变为红色
	return make_pair(End, true);

左单旋与AVL树的左单旋类似,旋转原理与右单旋图片相同,不在赘述

void _Single_Left(Node* parent)//左旋转

	//记录要移动的节点
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;

	//连接
	parent->_right = SubRL;
	if (SubRL != nullptr)
	
		SubRL->_parent = parent;
	
	SubR->_left = parent;
	Node* GradParent = parent->_parent;//记录这个节点的父节点,为了修改根节点
	parent->_parent = SubR;

	//调整根节点
	if (parent == _root)
	
		_root = SubR;
		SubR->_parent = GradParent;
	
	else //要旋转的节点是子树,修改GradParent指针
	
		if (GradParent->_left == parent)//旋转的是左子树,连接到左边
		
			GradParent->_left = SubR;
		
		else
		
			GradParent->_right = SubR;//反之
		
		SubR->_parent = GradParent;
	

④p是g的右子树 cur为红,p为红,g为黑,u存在且为红

这种情况与①相同,不在赘述

⑤p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g是一条直线)_单旋

⑥p是g的右子树 cur为红,p为红,g为黑,u不存在/u为黑(cur,p,g为折线)_双旋


根据上图可以写出红黑树插入的下半逻辑

红黑树的插入代码

pair<Node*, bool>Insert(const pair<Key, Value>val)//红黑树的插入

	if (_root == nullptr)
	
		_root = new Node(val);
		_root->col = BLACK;
		return make_pair(_root, true);
	
	//二叉搜索树插入
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur != nullptr)
	
		if (cur->_kv.first < val.first)
		
			parent = cur;
			cur = cur->_right;
		
		else if (cur->_kv.first > val.first)
		
			parent = cur;
			cur = cur->_left;
		
		else//键值重复,插入失败
		
			return make_pair(cur, false);
		
	
	cur = new Node(val);
	cur->col = RED;
	if (parent->_kv.first > val.first)
	
		parent->_left = cur;
	
	else
	
		parent->_right = cur;
	
	cur->_parent = parent;
	Node* End = cur;//记录插入位置的节点
	//控制路径
	while (parent != nullptr && parent->col == RED)
	
		Node* GradParent = parent->_parent;
		if (parent == GradParent->_left)//关键看Uncle节点的颜色
		
			Node* Uncle = GradParent->_right;
			//情况1:Uncle存在且为红
			if (Uncle && Uncle->col == RED)
			
				parent->col = Uncle->col = BLACK;
				GradParent->col = RED;
				cur = GradParent;
				parent = cur->_parent;
			
			else//Uncle不存在或Uncle存在为黑色
			
				if (cur == parent->_left)//右高,进行右旋
				
					_Single_Right(GradParent);
					GradParent->col = RED;
					parent->col = BLACK;
				
				else//折线形状,左右双旋
				
					_Single_Left(parent);
					_Single_Right(GradParent);
					cur->col = BLACK;
					parent->col = GradParent->col = RED;
				
				break;//旋转后黑色节点个数不变,直接跳出循环
			
		
		else//parent=GradParent->_right
		
			Node* Uncle = GradParent->_left;
			if (Uncle != nullptr && Uncle->col == RED)
			
				Uncle->col = parent->col = BLACK;
				GradParent->col = RED;
				cur = GradParent;
				parent = cur->_parent;
			
			手撕红黑树(Red-Black Tree)

手撕红黑树(Red-Black Tree)

红黑树之旅 | 手撕红黑树视频

100张图,手撕红黑树!

Java实现与红黑树

教你轻松理解红黑树的实现及原理