自学内容网 自学内容网

C++红黑树

目录

一、红黑树的概念

二、红黑树的性质

三、红黑树节点的定义

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

2. 检测新节点插入后,红黑树的性质是否造到破坏

情况一: cur为红,p为红,g为黑,u存在且为红

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

五、红黑树的验证

六、红黑树和AVL树的比较


一、红黑树的概念

        红黑树,是一种二叉搜索树,但是在每个节点上增加了一个存储位表示节点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个节点颜色的某种限制,红黑树保证了没有一条路径会比最短路径长出两倍,所以红黑树是一种接近平衡的搜索二叉树。

二、红黑树的性质

1.每个节点不是红色就是黑色

2.根节点是黑色的

3.如果一个节点是红色的,那么他的两个孩子节点是黑色的(即不能有连续的红色节点)

4.对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含一样数量的黑色节点

5.每个叶子节点都是黑色的(这里的叶子节点指的是空节点,也叫NIL节点)

三、红黑树节点的定义

enum Color
{
RED,BLACK
};
template<class K,class V>
struct RBTreeNode
{
typedef RBTreeNode<K, V> Node;
Node* _left;
Node* _right;
Node* _parent;
pair<K, V> _kv;
Color _col;
//构造函数
RBTreeNode(const pair<K,V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_col(RED)
{}

};

        为什么红黑树的节点默认是红色呢?因为如果插入黑色节点,会违反规则4,但是插入红色节点,只有可能影响规则3(不能有连续的红色节点),调整起来比较方便。

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

template<class ValueType>
class RBTree
{
  //……
bool Insert(const ValueType& data)
{
PNode& pRoot = GetRoot();
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;
      _pHead->_pParent = pRoot;
}
else
{
// 1. 按照二叉搜索的树方式插入新节点
      // 2. 检测新节点插入后,红黑树的性质是否造到破坏,
//  若满足直接退出,否则对红黑树进行旋转着色处理
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot(){ return _pHead->_pParent;}

private:
PNode _pHead;

2. 检测新节点插入后,红黑树的性质是否造到破坏

        因为新节点的默认颜色是红色,因此,:如果其父亲节点是黑色,就没有违反红黑树的性质,则不需要调整,可以直接插入。但是当父亲节点为红色的时候,则违反了性质三(不能有连续的红色节点),此时需要对红黑树进行分类讨论。

        约定:cur为当前节点,p为父节点,g为祖父节点,uncle为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

        但是对于情况二:我们需要分类讨论,因为cur在parent的左边和右边/parent在祖父的左边和右边所产生的旋转是不同的,一共需要分成4类来讨论

template<class K,class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
bool insert(const pair<K,V>& kv)
{
//先按照普通搜索二叉树的规则插入节点
if (_root==nullptr)
{
_root = new Node(kv);
return true;
}
else
{
//找到要插入的位置
Node* cur = _root;
Node* parent = cur->_parent;
while (cur!=nullptr)
{
if (kv.first<cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first>cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first==cur->_kv.first)
{
return false;
}
}
//cur是连接到parent的左边还是右边呢
cur = new Node(kv);
if (cur->_kv.first<parent->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else if (cur->_kv.first>parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
//根据红黑树的规则进行变色+旋转
//只有当父亲是红色且存在,才需要处理,因为父亲是黑色对上面没有影响可以直接插入
while (parent!=nullptr&&parent->_col==RED)
{
Node* grandparent = parent->_parent;
//parent==grandparent->_left
if (parent==grandparent->_left)
{
Node* uncle = grandparent->_right;
//叔叔存在且为红色
if (uncle!=nullptr&&uncle->_col==RED)
{
//变色
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//grandparent给cur继续向上调整
cur = grandparent;
parent = grandparent->_parent;
}
//叔叔不存在或者存在且为黑色
else
{
if (cur==parent->_left)
{
//右单旋
_RotateR(grandparent);
//变色
grandparent->_col = RED;
parent->_col = BLACK;

}
else
{
//双旋
_RotateL(parent);
_RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
//因为走到else结束后,parent右一种情况是是黑色,有一种情况是红色,但是都以及调整好了,
//不需要再往上调整,所以直接break
break;
}
}
//parent==grandparent->_right
else
{
Node* uncle = grandparent->_left;
if (uncle!=nullptr&&uncle->_col==RED)
{
//变色
uncle->_col = parent->_col = BLACK;
grandparent->_col = RED;
//继续往上处理
cur = grandparent;
parent = grandparent->_parent;
}
else
{
if (cur==parent->_right)
{
//左旋
_RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
//双旋
_RotateR(parent);
_RotateL(grandparent);
//变色
cur->_col = BLACK;
grandparent->_col = RED;

}
break;
}
}
}
}
//根必须要是黑色
_root->_col = BLACK;
return true;
}



//右单旋
void _RotateR(Node* parent)
{
//记录两个重要节点
Node* subL = parent->_left;
Node* subLR = subL->_right;

//连接
parent->_left = subLR;
//如果subL的右边为空,空不能解引用,需要注意一下
if (subLR->_right != nullptr)
{
subLR->_parent = parent;
}
subL->_right = parent;
//处理根的问题
Node* grandparent = parent->_parent;

parent->_parent = subL;
subL->_parent = grandparent;
//如果原本parent是根,则让_root=subL
if (parent == _root)
{
_root = subL;
subL->_parent = nullpptr;
}
//如果parent是子树,则连接subL和grandparent
else
{
if (grandparent->_right == parent)
{
grandparent->_right = subL;
}
else if (grandparent->_left == parent)
{
grandparent->_left = subL;
}
}

}
//左单旋
void _RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;

parent->_right = subRL;
subR->_left = parent;

Node* grandparent = parent->_parent;
parent->_parent = subR;
if (subRL != nullptr)
{
subRL->_parent = parent;
}
//处理根的问题
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == grandparent->_right)
{
grandparent->_right = subR;
}
else if (parent == grandparent->_left)
{
grandparent->_left = subR;
}
}

}
private:
Node* _root = nullptr;
};
}

五、红黑树的验证

        红黑树的验证检测分为两步:

(1)检测其是非满足二叉搜索树的规则(中序是否为有序)

(2)检测其是否满足红黑树的性质

bool isBalance()
{
if (_root==nullptr)
{
return true;
}
if (_root->_col==RED)
{
return false;
}

//求参考值
int refval = 0;
Node* cur = _root;
while (cur!=nullptr)
{
if (cur->_col==BLACK)
{
refval++;
}
cur = cur->_left;
}
return Check(_root, 0, refval);
}
//检查颜色是否符合规则和黑色节点的数量是否相同
//blacknum是根节点到当前节点路径上的黑色节点数量
bool Check(Node* root,int blacknum,const int refVal)
{
            //走到空的时候说明该路径已经走完了,比对balcknum的值
if (root==nullptr)
{
if (blacknum!=refVal)
{
cout << "存在不同路径的黑色节点数量不相等" << endl;
return false;
}
return true;
}
//因为检测孩子不好检测,有多种情况,所以我们反过来从孩子检测父亲的颜色
if (root->_col==RED&& root->_parent->_col == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
if (root->_col==BLACK)
{
blacknum++;
}
return Check(root->_left,blacknum,refVal)
&& Check(root->_right,blacknum,refVal);
}

六、红黑树和AVL树的比较

        红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,(不过像词典这种增删的比较少的项目,AVL由于其追求绝对平衡,所以查找的效率更高)而且红黑树实现比较简单,所以实际运用中红黑树更多。

        红黑树常常因为其高效的性能,而被用到map/set等数据结构中。


原文地址:https://blog.csdn.net/2303_79336820/article/details/142344424

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!