前置数据结构知识:
二叉树的概念;
二叉搜索树的概念和查找插入操作实现;
AVL树的概念和左旋右旋 左右旋 右左旋的操作,最好能够基本实现一下其代码;
红黑树是什么?它是前提是一个颗二叉搜索树,然后在二叉搜索树的前提下增加了一定的规则,使得红黑树能够成为一种接近于平衡的二叉搜索树,并且不失去其查找效率;
那么红黑树必须满足什么样的规则?
注意:红黑树这里的路径不是任意结点到叶子结点,而是到达所谓的NIL结点(其实就是空结点);
比如上面的路径如果是平时的树,就有5路径,而在红黑树有11条路径;
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,保证了任意一条路径长度都超过其他路径的两倍;
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
因为第1条该树上的节点非红即黑,由于第3条该树上不允许存在两个连续的红节点,那么对于从一个节点到其叶子节点的一条最长的路径一定是红黑交错的,那么最短路径一定是纯黑色的节点;而又第4条从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点,这么来说最长路径上的黑节点的数目和最短路径上的黑节点的数目相等!而又第2条根结点为黑、第5条叶子节点是黑,那么可知:最长路径长度<=2*最短路径长度。
为什么说红黑树不是严格平衡的二叉树,只能保证近视平衡?
从我们上面的红黑树规则就可以推出:最长路径不超过最短路径的两倍,我们就知道,它是接近平衡的,不像AVL树,它通过平衡因子控制到左右子树的高度差不超过1,属于严格平衡的那种!
虽然说红黑树的NIL结点必须是黑色的,但是一般我们平时画图思考不怎么考虑这个NIL结点,因为它毕竟是空结点(但是不考虑该结点你必须保证其画的红黑树正确性)
由于AVL树为了追求严格的平衡,那么它在插入的过程中,一旦插入过程高度差超过2,就会有频繁旋转的操作,旋转操作本身也是消耗时间的;
而红黑树不并控制高度严格平衡,旋转次数必定少于AVL树;
从扣细节角度讲,红黑树的查找效率确实没有AVL树高,但是对于CPU来说AVL的查找效率相对于红黑树提高也是微乎其微,根本毫无任何意义,并且AVL树插入时候为了达到高度平衡的旋转次数必定多余红黑树插入时候的旋转次数;
基于上面的原因:红黑树是比AVL树更优的一种选择!!!!!!!
插入的步骤:
首先必须有个认知:
往红黑树插入结点之前,该树就是一颗红黑树了;
往红黑树插入的结点,一定是在红黑树的叶子结点插入的;
插入结点成功后,才开始控制红黑树的颜色和平衡;
红黑树插入的结点:一定是红色的,因为插入红色结点,最多只是破坏了红黑树的规则3,只会影响的范围比较小;假如插入的结点是黑色的,那么久破坏了规则4,那么影响的范围太大了,一条路径多了一个黑色结点,整个红黑树的结构都会被破坏;
插入结点后的分析:
情况一:
假如有一颗红黑树没插入之前:
插入之后的分析:
情况二:1.
(图说的是左旋,其实这里是右旋,我写错了,但是我懒得截图,知道意思就行)
假如有一颗红黑树没插入之前:
假如一颗二叉树没插入之前:
插入之后:
情况三: cur为红,p为红,g为黑,存在且u为黑;
#pragma once
using namespace std;
enum Colour
{
RED,
BLACK
};
//红黑树的结点
template<class K,class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv; //数据域
Colour _col; //红黑树的颜色
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr), _right(nullptr),
_parent(nullptr), _kv(kv), _col(RED)
{}
};
template<class K,class V>
class RBTree
{
public:
typedef RBTreeNode<K, V> Node;
public:
RBTree() :_root(nullptr)
{}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
parentParent->_left = subR;
else
parentParent->_right = subR;
subR->_parent = parentParent;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
parentParent->_left = subL;
else
parentParent->_right = subL;
subL->_parent = parentParent;
}
}
void Inorder()
{
_Inorder(_root);
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
//检查是否为红黑树,只要检查其红黑树是否满足其规则就好
/*思考:如何判断这个条件*/
bool is_rbtree()
{
//如果根都是红色,一定不是红黑树
if (_root && _root->_col == RED)
{
cout << "根结点不是黑色" << endl;
return false;
}
/*如何验证每条路径黑色结点数量相等*/
//以最左路径为基准值:算出最左路径的黑色结点数量
int banchmark = 0;
Node* left = _root;
while (left)
{
if (left->_col == BLACK)
{
++banchmark;
}
left = left->_left;
}
int blackNum = 0; //从根到每个结点的黑色数量
return _is_rbtree(_root,banchmark,blackNum);
}
bool _is_rbtree(Node* root,int banchmark,int blackNum)
{
if (root == nullptr)
{
if (blackNum != banchmark)
{
cout << "出现一条路径的黑色结点处理不相等" << endl;
return false;
}
return true;
}
//如果该结点是红色,那么该结点一定有父亲
/*我们可以检查结点的父亲,如果也是红色,肯定不是红黑树*/
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "出现连续出现的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
{
++blackNum;
}
return _is_rbtree(root->_left, banchmark, blackNum)&&
_is_rbtree(root->_right, banchmark, blackNum);
}
bool insert(const pair<K, V>& kv)
{
//如果红黑树为空,直接新插入的结点作为根
if (_root == nullptr)
{
_root= new Node(kv);
_root->_col = BLACK;
return true;
}
//如果插入的结点但不是作为根,那么就寻找插入的位置
/*思考:如何寻找插入的位置,需要知道什么信息?*/
Node* parent = nullptr; //插入结点cur位置的父节点
Node* cur = _root;
//寻找要插入的位置
while (cur)
{
//要插入的结点位置,比当前结点大
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
//要插入的结点位置,比当前结点小
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
//要插入的结点位置,和当前结点相等
else
{
return false;
}
}
//找到要插入的位置
cur = new Node(kv); //要插入的结点
cur->_col = RED; //插入的结点设置为红色
//判断插入的位置是在父节点的左还是右
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent; //维持三叉链
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//结点插入成功后:开始控制平衡(红黑树的精髓)
while (parent && parent->_col == RED)
{
//父亲存在且为红色,祖父肯定存在
Node* grandfather = parent->_parent;
//找叔叔的位置:是在grandfather的左边还是右边呢?
/*我们可以通过父亲在grandfather的左边还是右边确定*/
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//找到叔叔的位置后,判断叔叔的颜色
/*思考:叔叔颜色是红色的处理方案是什么?*/
if (uncle && uncle->_col == RED)
{
//变色+继续向上调整
/*如何变色?变谁?*/
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续迭代
/*迭代后有一种可能:也就是父亲不存在了,那么就要推出循环了,推出循环把根变为黑色即可*/
cur = grandfather;
parent = cur->_parent;
}
//叔叔不存在或者叔叔存在且为黑
else
{
//这是在parent == grandfather->_left得前提下的
//cur == parent->_left;两个条件加起来使得可以右旋,
if (cur == parent->_left)
{
RotateR(grandfather); //右单旋
grandfather->_col = RED;
parent->_col = BLACK;
}
//这是在parent == grandfather->_left得前提下的
//cur == parent->_right;两个条件加起来使得可以左右双旋旋,
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//调整完就可以直接推出循环了,说明该子树都是平衡了
break;
}
}
else //uncle在grandfather的左子树(处理方式和上面基本没变)
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RotateL(grandfather); //左单旋
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//调整完就可以直接推出循环了,说明该子树都是平衡了
break;
}
}
}
//根为黑色,
_root->_col = BLACK;
return true;
}
private:
Node* _root;
};
呃呃,其实这片文章写得很烂,我自己都看不下去了;主要是最近没事太多事了,没多少时间写,构思如何排版,如何让读者看明白,所以这红黑树的文章,主要还是给自己看的;其他人可能看不懂我在写什么