【C++】红黑树的简单模拟实现

一. 什么是红黑树?

1. 基本概念

红黑树和AVL树类似,是对搜索树的优化。不同于AVL树的绝对平衡,红黑树是近似平衡,即对于每个节点的以它为根的所有路径,其中最长路径的节点数不大于最短路径节点数的2倍。

2. 红黑树的特性

  • 每个节点不是红色就是黑色,其中根节点为黑
  • 红色节点不连续,即红色节点的左右孩子只能为黑色
  • 每个节点,以它为根的所有路径中黑色节点的数量相同

以上特性,就是红黑树近似平衡的原因:

  1. 红色节点不连续,说明最短路径的节点都是黑色,最长路径的节点是红黑相间。
  2. 每条路径节的黑色节点个数必须相同,决定了最短路径不超过最长路径的2倍。
    在这里插入图片描述

二. 为什么要有红黑树?

为什么要有红黑树?下面通过红黑树和搜索树、平衡树的比较来回答这个问题。

1. 红黑树和搜索树

当插入有序数据时,搜索树会退化为单枝树从而导致查找效率降为线性。红黑树通过对各个节点颜色的控制和位置的调整,使得树永远处于近似平衡状态,保证了O(logN)的查找效率。
在这里插入图片描述

2. 红黑树和平衡树

二者都是对搜索树进行了平衡处理

  • 平衡树是绝对平衡:左右子树高度差的绝对值小于等于1
  • 红黑树是近似平衡:最长路径不大于最短路径的2倍

红黑树不追求绝对平衡,相对平衡树而言降低了插入和删除时旋转的次数,频繁的插入删除红黑树的性能更优,并且红黑树的实现也较为简单,所以实际运用中红黑树更多。如果插入删除的操作较少且需要频繁的查找,那么平衡树性能更优。

三. 红黑树插入操作实现

1. 基本框架

主要定义里两个类:节点类 + 树的本体,其中前者存储节点节点的相关信息,后者主要实现红黑树的功能,如插入、删除、查找等。

1.1 节点类框架

数据域:颜色(默认为红)、pair键值对
指针域:左孩子和右孩子、父亲

// 枚举定义颜色
enum COLOR
{
  RED,
  BLACK
};

// 节点的定义
template<class k, class v>
struct RBTreeNode
{
  // 构造函数,颜色默认给为红色
  RBTreeNode(const pair<k, v>& kv)
    :_color(RED)
    ,_kv(kv)
    ,_parent(nullptr)
    ,_left(nullptr)
    ,_right(nullptr)
  {}

  COLOR _color;
  pair<k, v> _kv;
  RBTreeNode<k, v>* _parent;
  RBTreeNode<k, v>* _left;
  RBTreeNode<k, v>* _right;
};

为什么颜色默认为红色?

因为红色处理起来更简单,对其他路径的波及较小。如果为黑色,想想看这条路径的黑色节点数量加了一个,那么其他路径都要调整来保证所有路径的黑色节点个数一致;但是如果置为红色,这时只有插入位置的父亲也为红才需要调整,即使调整也只需调整一条路径,比较容易。

1.2 树本体框架

只有一个成员变量就是一个节点的指针,代表树的根节点。也为只有一个成员变量,可以不用写构造函数,我们直接声明时给缺省值即可,就是把它置为nullptr。

// 树的定义
template<class k, class v>
class RBTree
{
public:
	typedef RBTreeNode<k, v> Node;
private:
	Node* _root = nullptr;// 声明时给缺省值
};

2. 第一步:按搜索树性质插入节点

按搜索树的性质来找到插入的位置,然后直接插入:

  • 如果是空树,则则该插入节点就作为整棵树的根,并把颜色改为黑色
  • 非空的话,按照搜索树的性质找到插入的位置和并记录父亲,然后和父亲的key比较来决定作为左孩子还是右孩子。
template<class k, class v>
class RBTree 
{
  public:
    typedef RBTreeNode<k, v> Node;
    
    bool Insert(const pair<k, v>& kv)
    {
      // 第一步:按搜索树规则插入一个节点
      // 1、空树的话插入节点作为根节点
      // 2、不空的话,按搜索树规则找到插入的位置,然后插入
      if(!_root)
      {
        _root=new Node(kv);
        _root->_color=BLACK;
        return true;
      }
      // 寻找插入的位置
      Node* parent=nullptr;
      Node* cur=_root;
      while(cur)
      {
        parent=cur;
        if(cur->_kv.first == kv.first)
        {
          return false;
        }
        else if(cur->_kv.first < kv.first)
        {
          cur=cur->_right;
        }
        else if(cur->_kv.first > kv.first)
        {
          cur=cur->_left;
        }
      }
      // 找到后插入
      Node* newNode=new Node(kv);
      newNode->_parent=parent;
      if(parent->_kv.first > kv.first)
      {
        parent->_left=newNode;
      }
      else if(parent->_kv.first < kv.first) 
      {
        parent->_right=newNode;
      }
      // 未完待续.....

	private:
		Node* _root=nullptr;
};

3. 第二步:调整节点的颜色

3.1 调整操作

走到了这一步,那么插入节点肯定不是整棵树的根了,因为空树情况在第一步里面已经处理好了,也就是说现在插入的新节点一定有父亲。根据父亲的颜色分两种情况处理:

  1. 情况一:父亲颜色为黑。这种情况就不用调整了,插入结束。
  2. 情况二:父亲颜色为红。需要调整

针对情况二,我们需要知道,这时一定是有祖父节点的,即父亲的父亲一定存在且为黑色,因为父亲的颜色为红不可能是根节点,又因为红色不连续所以祖父为黑。这时我们的调整取决于叔叔,当然叔叔可能存在也可能不存在,我们分情况处理。

约定插入节点为cur,父亲为parent,祖父为gfather,叔叔为uncle,此时:

  • uncle存在且为红 --》 把parent和uncle的颜色到改为黑,gfather颜色改为红,从gfather开始继续往上调整。
    在这里插入图片描述
  • uncle 不存在 or 存在且为黑 --》依照gfather、parent、cur的关系来旋转处理。
    在这里插入图片描述
// 拼接到第一步代码后面
// 第二步:调整节点的颜色
// 1、如果父亲颜色为黑就不用调整
// 2、如果父亲节点颜色为红,调整方式由叔叔决定
//    a、叔叔存在且为红 --> 把叔叔和父亲的颜色调整为黑,爷爷颜色调整为红,然后继续往上调整
//    b、叔叔不存在或存在且为黑 --> 看爷爷、父亲、和cur的相对位置来决定用什么旋转,旋转后交换父亲和爷爷的颜色
cur=newNode;
while(parent &&  parent->_color == RED)
{
  // 既然进来了这个循环内部,说明父亲一定是红色,那么一定有爷爷且爷爷颜色为黑
  Node* grandfather = parent->_parent;
  // 确定叔叔的位置
  if(grandfather->_left == parent)
  {
    Node* uncle=grandfather->_right;
    if(uncle && uncle->_color == RED)// 叔叔存在且为红
    {
      parent->_color=uncle->_color=BLACK;
      grandfather->_color=RED;
      cur=grandfather;
      parent=cur->_parent;
    }
    else // 叔叔不存在或者存在且为黑
    {
      if(cur == parent->_right)
      {
        RotateL(parent);
        std::swap(parent,cur);
      }
      RotateR(grandfather);
      parent->_color=BLACK;
      grandfather->_color=RED;
      break;
    }
  }
  else if(grandfather->_right == parent)
  {
    Node* uncle=grandfather->_left;
    if(uncle && uncle->_color==RED)// 叔叔存在且为红
    {
      uncle->_color=parent->_color=BLACK;
      grandfather->_color=RED;
      cur=grandfather;
      parent=cur->_parent;
    }
    else // 叔叔不存在或存在且为黑
    {
      if(cur == parent->_left)
      {
        RotateR(parent);
        std::swap(parent,cur);
      }
      RotateL(grandfather);
      parent->_color=BLACK;
      grandfather->_color=RED;
      break;
    }
  }
}
_root->_color=BLACK;
}

// 左单旋
void RotateL(Node* parent)
{
  Node* subR=parent->_right;
  Node* subRL=subR->_left;
  parent->_right=subRL;
  if(subRL != nullptr)
  {
    subRL->_parent=parent;
  }
  subR->_left=parent;
  Node* pparent=parent->_parent;
  parent->_parent=subR;
  if(pparent == nullptr)
  {
    _root=subR;
    subR->_parent=nullptr;
  }
  else 
  {
    subR->_parent=pparent;
    if(pparent->_left == parent)
    {
      pparent->_left=subR;
    }
    else if(pparent->_right == parent)
    {
      pparent->_right=subR;
    }
  }
}

// 右单旋
void RotateR(Node* parent)
{
  Node* subL=parent->_left;
  Node* subLR=subL->_right;

  parent->_left=subLR;
  if(subLR != nullptr)
  {
    subLR->_parent=parent;
  }
  subL->_right=parent;
  Node* pparent=parent->_parent;
  parent->_parent=subL;
  if(pparent == nullptr)
  {
    _root=subL;
    subL->_parent=nullptr;
  }
  else 
  {
    subL->_parent=pparent;
    if(pparent->_left == parent)
    {
      pparent->_left=subL;
    }
    else if(pparent->_right == parent) 
    {
      pparent->_right=subL;
    }
  }

3.3 调整总结

在这里插入图片描述

4. 完整代码

#pragma once

#include <iostream>

using std::cin;
using std::cout;
using std::endl;
using std::pair;

// 枚举定义颜色
enum COLOR
{
  RED,
  BLACK
};

// 节点的定义
template<class k, class v>
struct RBTreeNode
{
  // 构造函数,颜色默认给为红色
  RBTreeNode(const pair<k, v>& kv)
    :_color(RED)
    ,_kv(kv)
    ,_parent(nullptr)
    ,_left(nullptr)
    ,_right(nullptr)
  {}

  COLOR _color;
  pair<k, v> _kv;
  RBTreeNode<k, v>* _parent;
  RBTreeNode<k, v>* _left;
  RBTreeNode<k, v>* _right;
};

// 树的定义
template<class k, class v>
class RBTree 
{
  public:
    typedef RBTreeNode<k, v> Node;

    // 插入一个节点
    bool Insert(const pair<k, v>& kv)
    {
      // 第一步:按搜索树规则插入一个节点
      // 1、空树的话插入节点作为根节点
      // 2、不空的话,按搜索树规则找到插入的位置,然后插入
      if(!_root)
      {
        _root=new Node(kv);
        _root->_color=BLACK;
        return true;
      }
      // 寻找插入的位置
      Node* parent=nullptr;
      Node* cur=_root;
      while(cur)
      {
        parent=cur;
        if(cur->_kv.first == kv.first)
        {
          return false;
        }
        else if(cur->_kv.first < kv.first)
        {
          cur=cur->_right;
        }
        else if(cur->_kv.first > kv.first)
        {
          cur=cur->_left;
        }
      }
      // 找到后插入
      Node* newNode=new Node(kv);
      newNode->_parent=parent;
      if(parent->_kv.first > kv.first)
      {
        parent->_left=newNode;
      }
      else if(parent->_kv.first < kv.first) 
      {
        parent->_right=newNode;
      }
      // 第二步:调整节点的颜色
      // 1、如果父亲颜色为黑就不用调整
      // 2、如果父亲节点颜色为红,调整方式由叔叔决定
      //    a、叔叔存在且为红 --> 把叔叔和父亲的颜色调整为黑,爷爷颜色调整为红,然后继续往上调整
      //    b、叔叔不存在或存在且为黑 --> 看爷爷、父亲、和cur的相对位置来决定用什么旋转,旋转后交换父亲和爷爷的颜色
      cur=newNode;
      while(parent &&  parent->_color == RED)
      {
        // 既然进来了这个循环内部,说明父亲一定是红色,那么一定有爷爷且爷爷颜色为黑
        Node* grandfather = parent->_parent;
        // 确定叔叔的位置
        if(grandfather->_left == parent)
        {
          Node* uncle=grandfather->_right;
          if(uncle && uncle->_color == RED)// 叔叔存在且为红
          {
            parent->_color=uncle->_color=BLACK;
            grandfather->_color=RED;
            cur=grandfather;
            parent=cur->_parent;
          }
          else // 叔叔不存在或者存在且为黑
          {
            if(cur == parent->_right)
            {
              RotateL(parent);
              std::swap(parent,cur);
            }
            RotateR(grandfather);
            parent->_color=BLACK;
            grandfather->_color=RED;
            break;
          }
        }
        else if(grandfather->_right == parent)
        {
          Node* uncle=grandfather->_left;
          if(uncle && uncle->_color==RED)// 叔叔存在且为红
          {
            uncle->_color=parent->_color=BLACK;
            grandfather->_color=RED;
            cur=grandfather;
            parent=cur->_parent;
          }
          else // 叔叔不存在或存在且为黑
          {
            if(cur == parent->_left)
            {
              RotateR(parent);
              std::swap(parent,cur);
            }
            RotateL(grandfather);
            parent->_color=BLACK;
            grandfather->_color=RED;
            break;
          }
        }
      }
      _root->_color=BLACK;
    }

 private:
    // 左单旋
    void RotateL(Node* parent)
    {
      Node* subR=parent->_right;
      Node* subRL=subR->_left;
      parent->_right=subRL;
      if(subRL != nullptr)
      {
        subRL->_parent=parent;
      }
      subR->_left=parent;
      Node* pparent=parent->_parent;
      parent->_parent=subR;
      if(pparent == nullptr)
      {
        _root=subR;
        subR->_parent=nullptr;
      }
      else 
      {
        subR->_parent=pparent;
        if(pparent->_left == parent)
        {
          pparent->_left=subR;
        }
        else if(pparent->_right == parent)
        {
          pparent->_right=subR;
        }
      }
    }

    // 右单旋
    void RotateR(Node* parent)
    {
      Node* subL=parent->_left;
      Node* subLR=subL->_right;

      parent->_left=subLR;
      if(subLR != nullptr)
      {
        subLR->_parent=parent;
      }
      subL->_right=parent;
      Node* pparent=parent->_parent;
      parent->_parent=subL;
      if(pparent == nullptr)
      {
        _root=subL;
        subL->_parent=nullptr;
      }
      else 
      {
        subL->_parent=pparent;
        if(pparent->_left == parent)
        {
          pparent->_left=subL;
        }
        else if(pparent->_right == parent) 
        {
          pparent->_right=subL;
        }
      }
    }

    Node* _root=nullptr;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值