AVL树/红黑树

1 二叉搜索树

二叉搜索树 又叫二叉排序树

特点
1: 左子树上的所有节点小于 根节点的值,右子树上的所有节点大于根节点的值。

		2  空树也是二叉搜索树。
		3  二叉搜索树不存在值相等的节点。
		4  二叉搜索树的左右子树也分别是二叉搜索树。

eg:
在这里插入图片描述
二叉搜索树的操作 :

	1  查找:
		根据二叉搜索树的特点,可以进行判断
		大于根,就在右子树中找,
		小于根 ,左子树中找。
	
	2  插入 
		1  树为空 ,直接插入。
		2 不为空,找自己插入的位置 ,若已存在,插入失败。

二叉排序树的插入一定是在叶子节点上进行的。

	3  删除 
		 删除分情况 :
		 	a : 若删除的节点不存在,则返回失败,
		 	b	:删除的节点无左子树,将孩子连给父亲
		 	c  : 	删除的节点没有右子树,将孩子连给父亲
		 	d:	既有左子树,又有右子树,直接删不好删,**我们采用替代删除的思想,**
		 
		 	即   要不然删除该节点左子树中最大的-----即左子树中中序遍历最后一个节点,或者,右子树中最小的,
		 
		 	做法:先找到一个替代节点,将该节点的值赋给原来要删的节点,再删掉替代结点就行。

4 二叉搜索树的模拟实现

#include <stdio.h>
#include <iostream>
#include <stack>

//结点的组成
template <class T>//类似于模板类  ,模板结构体
struct BSTNode
{
  BSTNode(const T& data)
    :pLeft_(nullptr)
     ,pRight_(nullptr)
     ,pParent_(nullptr)
     ,data_(data)
     ,buf_(0)
  {}

  BSTNode<T>* pLeft_;
  BSTNode<T>* pRight_;
  BSTNode<T>*  pParent_;
  T data_;
  int buf_;//平衡因子
};


template <class T>
class BSTree
{
  typedef BSTNode<T> Node;//取别名
  typedef Node* PNode;//结点指针
  
  public:

  BSTree()
  :pNode_(nullptr)
  {}


  //后序递归销毁
 void Destroy(PNode pRoot_)
{
  if(pRoot_->pLeft_ == nullptr  && pRoot_->pRight_ == nullptr)
    {
      free(pRoot_);
      pRoot_ = nullptr;//这一步还是很重要要的,1避免出现野指针,2  使递归回归时能正常进行
      return ;
    }

      if(pRoot_->pLeft_)
        Destroy(pRoot_ ->pLeft_);
      if(pRoot_ ->pRight_)
        Destroy(pRoot_->pRight_);
}

~BSTree()
{ 
  Destroy(); 
}


      //用二叉搜索树的特性查找 data 元素 ,找到返回位置指针 ,未找到 返回nullptr
  //Find1 递归 //数据大 ,性能差 
PNode Find1(const T& data)const 
{
     if(pNode_ == nullptr)
     {
       return nullptr;
     }

     PNode tmp =pNode_;
     if(tmp->data_ == data)
     {
       return tmp;
     } 
    
     else if(tmp->pLeft_ ==nullptr && tmp->pRight_ == nullptr)
     {
       return nullptr;
     }
      else if(tmp->pLeft_ == nullptr && tmp->data_ >data )
      {
        return  nullptr;
      }
      else if(tmp->pRight_ == nullptr && tmp->data_ < data)
      {
        return nullptr;
      }
    else if(data > pNode_->data_)
    {
      tmp = tmp->pRight_;
      Find (data); 
    }

    else 
    {
      tmp = tmp ->pLeft_;
      Find(data);
    }
}

//Find  非递归
PNode Find(const T& data)
{
  if (pNode_ == nullptr)
    return nullptr;

  PNode cur = pNode_;
  while(cur)
  {
      if(cur->data_ == data )
        return cur;
      else if(cur-> data_ > data)
      {
        cur = cur->pRight_ ; 
      }

      else 
      {
        cur = cur ->pLeft_;
      }
  }
  return cur;
}

    bool insert(const T& data)
{
  //1  空树
  if(pNode_ ==nullptr)
  {
     pNode_ = new PNode(data);
    return true;
  }

    PNode parent  = nullptr;//原来红黑树中不存在data, 就要插入data,并和原树 关联
    PNode  cur =pNode_;
  //2  比根小
    while(cur)//一直一直找 ,找到当前树为空的情况     
    {
      parent = cur;//提前保存双亲的位置

      if(data < cur->data_)
      {
        cur =  cur->pLeft_;
      }
      else if(data > cur->data_)
      {
        cur = cur->pRight_;
      }
      else//说明存在 
      {
        return false;
      }
    }
      //走到这里说明data不存在 ,找到了自己该待的位置了
      PNode tmp = new PNode(data);
      //给自己找双亲
     if(data > parent->data_) 
       parent->pRight_ = tmp;
     else 
       parent->pLeft_ = tmp;

      return true;
    }

bool erase(const T& data)
{
  PNode ret  = Find(data);
  if(ret == nullptr)
  {
    perror("不存在 data");
    return false;
  }
  
  if(ret->pLeft_ == nullptr && ret->pRight_ ==nullptr)//只有一个根节点
  {
      delete ret;
      return true;
  }
  else if(ret ->pLeft_ == nullptr)//左子树为空
  {
    PNode tmp = ret->pRight_;
    delete ret;
    ret =tmp;
    return true;
  }
  else if(ret->pRight_ == nullptr) //右子树为空
  {
    PNode tmp = ret ->pLeft_;
    delete ret;
    ret = tmp;
    return true;
  }
          //左右子树都存在,直接删,不好删,需要调整好多结点的位置,换种思想   删除替代结点的方式---删除左子树最大的结点,先交换值
          //或者 删除右子树最小的结点 ,也需要先交换值

  else //左右子树都存在
  {
    PNode  tmp = ret->pLeft_;
    PNode parent = nullptr;
    while(tmp)
    {
      parent = tmp;
      if(tmp->pRight_)
        tmp = tmp->pRight_;
      else 
        tmp = tmp->pLeft_;
    }

    ret->data_ =  parent->data_;
    delete parent;
    return true;
  }
}


//中序遍历
void Inorder( PNode& pNode_ )const 
{
  
  PNode cur = pNode_;

  if( cur == nullptr)
      return ;
  Inorder(pNode_->pLeft_);
  std::cout<<cur->data_<<"  ";
  Inorder(pNode_->pRight_);
  
}


//非递归 
void Inorder2()
{
  std::stack<std::pair<PNode,bool> >s;

  s.push(make_pair(pNode_,false));//相当于 用  true  标记了 每一层的根节点
  while(!s.empty())
  {
      PNode tmp = s.top().first;
      bool ret = s.top().second; 
      s.pop();
      if(tmp == nullptr)//当前结点为空 ,则跳过
      {
        continue;
      }

      if(ret)//查看标记
      {
        std::cout<<tmp->data_<<"  ";
      }
      else//注意栈 的 特性  
      {
        s.push(make_pair(tmp->pRight_,false));
        s.push(make_pair(tmp,true));//根结点用 true 标记
        s.push(make_pair(tmp->pLeft_,false));
      }
  }

}

5 二叉排序树的性能分析

二叉搜索树的 插入 和 删除操作都需要进行查找,,所以查找的效率代表了二叉搜索树的各个操作的效率。

对于一个关键码集合,如果个个关键码插入的次序 不相同,那么得到的二叉搜索树 也不一样

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树Log N/2
最坏情况下 退化为单支树,N/2

为了避免形成单支树 使性能最差,我们采用引入AVL树

AVL树

什么是AVL 树
1 首先是一颗二叉树
2 其次 左右子树的高度差(平衡因子)不超过1
3 左右子树也必须是AVL树

在这里插入图片描述

AVL树是高度平衡的,所以搜索的时间复杂度为O(log2 N)

AVL树的插入 :
像二叉搜索树的插入一样 ,可以进行插入。只不过在二叉搜索树的基础上 增加了平衡因子。
插入完成后,肯能会导致原树的平衡因子发生变化,所以我们插入后可能需要 进行调整平衡因子。

插入后 平衡因子的三种情况: 0 -1 1 2 -2

a 当平衡因子为0 时。不用调整,
b 当平衡因子为1 或 -1 时,需要向上调整,
c 当平衡因子为 -2 或 2 时,只可能出现在插入d 节点的爷爷节点位置,此时进行响应调整 。

调整方案: 旋转

需要进行调整的情况: 平衡因子为2 或 -2 时

四种情况:
均以 插入在 2 或 -2 平衡因子节点的相对位置而言:

	a:左子树的左子树     ----   右旋
	b:右子树的右子树  	----	左旋
	c:左子树的右子树		----	先左旋,再右旋
	d:右子树的左子树		----   先右旋,在左旋

四种情况下对应四种调整方案:

1 左旋 :(根从左边旋过来)
在这里插入图片描述

2 右旋 : (根从右边旋过来)
在这里插入图片描述

3 先左旋,再右旋
4 先右旋,再左旋

注意:
旋转处理完毕后 ,根据 插入节点的父节点的之前的情况 调整 父节点的爷爷节点 和 父节点的父节点父节点的 平衡因子。

AVL树的删除:类似于平衡二叉树的删除,只不过多了调节平衡因子, ,但删除节点后平衡因子的更新,最坏情况下一直要调整到根节点。

实现:

#include <stdio.h>
#include <iostream>
#include <stack>

//结点的组成
template <class T>//类似于模板类  ,模板结构体
struct BSTNode
{
  BSTNode(const T& data)
    :pLeft_(nullptr)
     ,pRight_(nullptr)
     ,pParent_(nullptr)
     ,data_(data)
     ,buf_(0)
  {}

  BSTNode<T>* pLeft_;
  BSTNode<T>* pRight_;
  BSTNode<T>*  pParent_;
  T data_;
  int buf_;//平衡因子
};


template <class T>
class BSTree
{
  typedef BSTNode<T> Node;//取别名
  typedef Node* PNode;//结点指针
  
  public:

  BSTree()
  :pNode_(nullptr)
  {}


  //后序递归销毁
 void Destroy(PNode pRoot_)
{
  if(pRoot_->pLeft_ == nullptr  && pRoot_->pRight_ == nullptr)
    {
      free(pRoot_);
      pRoot_ = nullptr;//这一步还是很重要要的,1避免出现野指针,2  使递归回归时能正常进行
      return ;
    }

      if(pRoot_->pLeft_)
        Destroy(pRoot_ ->pLeft_);
      if(pRoot_ ->pRight_)
        Destroy(pRoot_->pRight_);
}

~BSTree()
{ 
  Destroy(); 
}


      //用二叉搜索树的特性查找 data 元素 ,找到返回位置指针 ,未找到 返回nullptr
  //Find1 递归 //数据大 ,性能差 
PNode Find1(const T& data)const 
{
     if(pNode_ == nullptr)
     {
       return nullptr;
     }

     PNode tmp =pNode_;
     if(tmp->data_ == data)
     {
       return tmp;
     } 
    
     else if(tmp->pLeft_ ==nullptr && tmp->pRight_ == nullptr)
     {
       return nullptr;
     }
      else if(tmp->pLeft_ == nullptr && tmp->data_ >data )
      {
        return  nullptr;
      }
      else if(tmp->pRight_ == nullptr && tmp->data_ < data)
      {
        return nullptr;
      }
    else if(data > pNode_->data_)
    {
      tmp = tmp->pRight_;
      Find (data); 
    }

    else 
    {
      tmp = tmp ->pLeft_;
      Find(data);
    }
}

//Find  非递归
PNode Find(const T& data)
{
  if (pNode_ == nullptr)
    return nullptr;

  PNode cur = pNode_;
  while(cur)
  {
      if(cur->data_ == data )
        return cur;
      else if(cur-> data_ > data)
      {
        cur = cur->pRight_ ; 
      }

      else 
      {
        cur = cur ->pLeft_;
      }
  }
  return cur;
}


//我们  为了容器而实现红黑树 ,所以保证接口一至  bool 
    bool insert(const T& data)
{
  //1  空树
  if(pNode_ ==nullptr)
  {
     pNode_ = new PNode(data);
    return true;
  }

    PNode parent  = nullptr;//原来红黑树中不存在data, 就要插入data,并和原树 关联
    PNode  cur =pNode_;
  //2  比根小
    while(cur)//一直一直找 ,找到当前树为空的情况     
    {
      parent = cur;//提前保存双亲的位置

      if(data < cur->data_)
      {
        cur =  cur->pLeft_;
      }
      else if(data > cur->data_)
      {
        cur = cur->pRight_;
      }
      else//说明存在 
      {
        return false;
      }
    }
      //走到这里说明data不存在 ,找到了自己该待的位置了
      PNode tmp = new PNode(data);
      //给自己找双亲
     if(data > parent->data_) 
       parent->pRight_ = tmp;
     else 
       parent->pLeft_ = tmp;

      return true;
    }

bool erase(const T& data)
{
  PNode ret  = Find(data);
  if(ret == nullptr)
  {
    perror("不存在 data");
    return false;
  }
  
  if(ret->pLeft_ == nullptr && ret->pRight_ ==nullptr)//只有一个根节点
  {
      delete ret;
      return true;
  }
  else if(ret ->pLeft_ == nullptr)//左子树为空
  {
    PNode tmp = ret->pRight_;
    delete ret;
    ret =tmp;
    return true;
  }
  else if(ret->pRight_ == nullptr) //右子树为空
  {
    PNode tmp = ret ->pLeft_;
    delete ret;
    ret = tmp;
    return true;
  }
          //左右子树都存在,直接删,不好删,需要调整好多结点的位置,换种思想   删除替代结点的方式---删除左子树最大的结点,先交换值
          //或者 删除右子树最小的结点 ,也需要先交换值

  else //左右子树都存在
  {
    PNode  tmp = ret->pLeft_;
    PNode parent = nullptr;
    while(tmp)
    {
      parent = tmp;
      if(tmp->pRight_)
        tmp = tmp->pRight_;
      else 
        tmp = tmp->pLeft_;
    }

    ret->data_ =  parent->data_;
    delete parent;
    return true;
  }
}


//中序遍历
void Inorder( PNode& pNode_ )const 
{
  
  PNode cur = pNode_;

  if( cur == nullptr)
      return ;
  Inorder(pNode_->pLeft_);
  std::cout<<cur->data_<<"  ";
  Inorder(pNode_->pRight_);
  
}


//非递归 
void Inorder2()
{
  std::stack<std::pair<PNode,bool> >s;

  s.push(make_pair(pNode_,false));//相当于 用  true  标记了 每一层的根节点
  while(!s.empty())
  {
      PNode tmp = s.top().first;
      bool ret = s.top().second; 
      s.pop();
      if(tmp == nullptr)//当前结点为空 ,则跳过
      {
        continue;
      }

      if(ret)//查看标记
      {
        std::cout<<tmp->data_<<"  ";
      }
      else//注意栈 的 特性  
      {
        s.push(make_pair(tmp->pRight_,false));
        s.push(make_pair(tmp,true));//根结点用 true 标记
        s.push(make_pair(tmp->pLeft_,false));
      }
  }

}





//四种情况需要 旋转处理

//1  左左   右单旋
void rotateR(PNode parent)
{

  //插入左左 ,旋转 左右
  PNode subL = parent->pLeft_;//备份 左子树
  PNode subLR = subL->pRight_;  // 备份 左子树的右子树
  
  parent->pLeft_  = subLR;//   1  .将左孩子的右孩子赋值给  根的左孩子 

  if(subLR != nullptr)//  
  { 
    subLR->pParent_ = parent;//     更新他的父节点指向
  }
    
  subL->pRight_ = parent;   //  2   将根剪切到subLR的位置
  
  parent->pParent_ = subL;//更新 parent 当前的父节点

  PNode ppParent = parent->pParent_;//原来的父节点
  subL->pParent_ = ppParent;    //  更新父节点


  if(ppParent  == nullptr)//  3   若 parent 结点为根节点
  {         
    pNode_ = subL;
    subL->pParent_ = nullptr; //  更新父节点
  }
                                //  4 parent 非根,更新其父节点的相应子树指针
  else//不是根,parent可能是他栓亲的左子树 , 也可能是双亲的右子树
  {
      if(ppParent->pLeft_ == parent )//是双亲的左孩子
        ppParent->pLeft_  = subL;
      else 
        ppParent ->pRight_ = subL;
  }

  parent->buf_ = subL->buf_  = 0;//当亲调整后平衡因子为0 
}


//2  右右 ---- 左单旋
void rotateL(PNode parent)
{
    //插入右右 ,旋转右左
    PNode subR  = parent->pLeft_;
    PNode subRL = subR->pLeft_;

    parent ->pLeft_ = subRL;

    if(subRL != nullptr)
    {
        subRL->pParent_ = parent;
    }
    
    
    subR->pLeft_  = parent;

    parent->pParent_ = subR;

    PNode ppParent = parent->pParent_;
    subR->pParent_  = ppParent;

    if(ppParent == nullptr)//parent  为根
    {
      pNode_ = subR;
      subR->pParent_ = nullptr;
    }

    else //  parant 不为根结点
    {
      if(parent == ppParent->pLeft_)
      {
        ppParent->pLeft_ = subR;
      }
      else 
      {
        ppParent ->pRight_ = subR;
      }
    }
}
//3    插入较高左子树的右子树    先左单旋  ,再右单旋
void rotateLR(PNode parent)
{

  PNode subL = parent->pLeft_;
  PNode subLR = subL->pRight_;

  int buf = subLR->buf_;

  rotateL(parent->pLeft_);

  rotateR(parent);

//根据插入后 subLR的 平衡因子  调整其他平衡因子 ,变为0 的平衡因子不用处理,我们在后序函数中统一处理

  if(buf == 1)//  subLR 右边高,
  {
    subL->buf_ = -1;//旋转之后, subLR 称为  sub 的左子树
  }

  else if(buf == -1) 
  {
    parent->buf_ = 1;
  }
  else //   插入后 平衡因子是0  //不存在,既然需要旋转处理 ,那一定是平衡因子出问题 ,而且只能是插入结点的爷爷结点为平衡因子为+2  / -2,
       // 那么插入节点的父节点平衡因子== +1 / -1                //因我们处理平衡因子 是从底向上处理的
  { ; }
}


//4 插入到较高右子树的左子树 --- 右左   先 右旋转 ,再左旋转
void rotateRL(PNode parent)
{
  PNode subR = parent->pRight_;
  PNode subRL = subR->pLeft_;

  int buf = subRL ->buf_;

  rotateR(parent->pRight_);
  rotateL(parent);
  
  if(buf == 1)
  {
      parent->buf_ = -1;
  }

  else if(buf == -1)
  {
    subR->buf_ = 1;
  }
}


//  原理   AVL树的插入原理 
//    1   像二叉搜索树那样 正常插入 
//    2   插入成功之后 可能会导致原有节点的 平衡因子 发生变化  ,所以要进行调整平衡因子
//    3   插入后平衡因子 可能 会有 0  -1   1   2  -2 这几种情况  
//    ,若为 0  则正常 退出 ,  
//       若为1  -1  则进行向上调整平衡因子
//       若为 2  -2   说明 该问题一定发生在 爷爷节点上 ,进行旋转处理

bool Insert(const T& data)
{
    int ret = insert(data);
    if(ret  == false)
    {
      std::cout<<"data  已存在"<<std::endl;
      return  true;
    }
  //  否则插入成功
   
    PNode  cur =    Find(data);
    PNode  parent = cur->pParent_;
   
    while(parent)
   {

     //出入到 父节点哪边,平衡因子做相应的调整
      if(cur == parent->pLeft_ )
          --(parent->buf_);

      else                    
          ++(parent->buf_);

      if(parent->buf_ == 0)//正常
      {
          break;//一切正常 ,   原有所有的节点的平衡因子都不用处理,   结束
      }

      else if(parent ->buf_ == -1 || parent->buf_ == 1)//说明 插入前双亲为根的二叉树平衡因子 0  ,插入后 多了一层,因此需要向上调整
      {
          parent = parent->pParent_;
      }
          // 平衡因子为 2  或者 -2  只可能出现在 插入节点的 爷爷节点上
      else if(parent ->buf == 2)//插入到了右边,需要调整 ,接着判断 1  右旋 ?   2  先右旋再左旋?
      {
          if(cur == (parent->pRight_)->pRight_)//插入 右右  ,右旋转
            rotateL(parent);
        
          else // 插入 右 左       先右旋转 ,再左旋转
            rotateRL(parent);

          //继续向上调整
          parent = parent->pParent_;  
      }


      else //    只可能是 -2,   判断 左旋 还是 先左  后右
      {
          
          //插入到左子树的左子树     右旋转  
          if(cur == (parent->pLeft_)->pLeft_) 
            rotateR(parent);
         
          else //  插入到左子树的右子树    先 左旋转 再右旋转
            rotateLR(parent);
        
        //继续向上调整  
            parent = parent->pParent_;
      }

   }
}
private:

    PNode pNode_;
};

AVL树的性能方面:

AVL树是严格的平衡二叉排序树,所以查找效率非常高 O(log 2 N),

但对AVL树进行修改时,性能非常差,

插入时,为了维护其绝对平衡 ,就可能进行多次旋转。

最差的情况是在删除时, 可能一直要让旋转持续到根节点的位置。

红黑树

红黑树是一种自平衡二叉查找树

	所谓自平衡 ,就是说明 红黑树不一定是AVL树,红黑树自平衡,(从根到叶子,黑色节点数相同)

为什么 学习红黑树?
因为效率问题 :(当然这是后话)可以参考各自的优缺点。

红黑树和AVL树的优缺点

1 如果插入一个节点引起了树的不平衡,AVL树和红黑树都最多两次即可解决问题。 但在删除一个节点引起树的不平衡,最坏情况下,AVL树需要从最深的不平衡节点开始一直调整,直到调整到这条路径所有节点均平衡,即AVL树需要若干次旋转调整。而红黑树最多需要三次旋转即可自平衡。

2 AVL树是高度平衡的二叉搜索树,因此AVL树的查找效率能比红黑树快一丢丢,*(最多快一次),因为红黑树没有AVL树那样高度平衡,但最多只是稍不平衡多出1层,也就意味着,红黑树比AVL树查找的次数最多多一次。

3 AVL树在大量数据插入和删除时,调整的次数比红黑树要多,因此,红黑树在大量数据的插入和删除时效率更高。

相对AVL树 ,红黑树牺牲了部分平衡性,换取了插入和删除时所导致旋转的 效率问题的解决。

红黑树的特点
1 所有的节点不是红色就是黑色(废话嘛)
2 根是黑色的
3 所有的 空叶子 都为黑色(废话)
4 红色的节点的子节点必须是黑色的。
5 从根节点到任意一个叶子节点,黑色节点的数目是 相等的。

总之 1 红色节点不能相邻
2 根节点是黑色
3 从根节点到任意叶子节点,黑色节点数相等

默认 创建一颗红黑树所有节点都是红色

红黑树的插入

请注意: 每种情形下还有对称的一面

情形一 :树为空树
直接插入 做根,颜色改为黑色。

情形二 :插入位置的父节点为黑色,直接插入即可。

情形三:当该插入位置 节点的的父节点和 叔叔节点都是红色:(即颜色对称)

如下 : 插入 24
在这里插入图片描述
处理方式:将叔叔 节点 和 父节点都改为黑色,爷爷节点改为红色 。然后递归处理,直到处理完根节点(必须保证根节点为黑色)

处理原则 :1	两个红色不能相邻,
				   2	时刻注意父节点和叔叔节点的颜色。
				   	若父亲和自己同为红色,父亲和叔叔也同为红色,则将父亲和叔叔改色,爷爷也改色。(根节点颜色不变)

在这里插入图片描述

**情形四 :
1 叔叔和父亲不同色,
2 自己是父亲的右子树,父亲是爷爷的左子树 (对称情况: 自己是父亲的左子树,父亲是爷爷的右子树) **
在这里插入图片描述

处理方式:对父亲进行旋转,处理成情形五的样子。
		 
		  非左旋即右旋,视情况而定。

此时情况就成了情形五。

情形五:
1 父亲叔叔不同色。
2 自己是父亲的左子树,父亲是爷爷的左子树。 (对称情况: 自己是父亲的右子树,父亲是爷爷的右子树。)
在这里插入图片描述
处理方式:视具体情况进行旋转,并改色。

红黑树 只有两种旋转:
1 左旋,
2 右旋,

注意:1 为了后续实现关联式容器,红黑树的实现中增加一个头节点,Head节点 ,Root的双亲
2 为了区分Head节点与根节点 ,将Head的双亲指向Root 根节点 ,同时,将Head头节点的做孩子指向Root最左侧的,即最小的节点,Head的右节点指向Root最右侧的节点,即最大的节点。

在这里插入图片描述

红黑树的插入操作

红黑树的插入操作 根二叉搜索树一致,但不同的是,插入新节点后,可能毁坏红黑树的自平衡。因此 ,插入完成后,还可能需要进行相应的旋转 + 改色

实现

#include <iostream>

enum  Color{RED, BLACK};

template <class T>
struct RBTreeNode
{
  RBTreeNode(const T& data = T() , Color color = RED)
    :pLeft_(nullptr)
     ,pRight_(nullptr)
     ,pParent_(nullptr)
     ,data_(data)//平衡因子
     ,color_(color)//默认是红色结点 ,因为黑色节点必然会影响自平衡
  {}


  RBTreeNode<T> * pLeft_;
  RBTreeNode<T> * pRight_;
  RBTreeNode<T> * pParent_;
  T data_;
  Color color_;
};


// 红黑树实在二叉搜索树的基础上增加 平衡条件, 因此 红黑树的插入分两步, 1  二叉搜索树的插入 ,  2  平衡调整


//注意 ,为了实现后续的关联式容器 ,我们在红黑树的实现中加上了 Head_  头节点   Root->pParent_ ==  Head
template <class T>
class RBTree 
{

  typedef RBTreeNode<T>  Node;
  typedef Node* PNode;

  public:





  //右旋
  //我这里命名不科学,切勿自带感情 ,参数grandpa 最好用 pNode 代替
  void rotateR1(PNode& grandpa)
  {
    PNode ggrandpa = grandpa->pParent_;//爷爷的父亲
    PNode parent = grandpa->pLeft_; 
    PNode uncle = grandpa ->pRight_;

    grandpa->pLeft_ = parent->pRight_;
    parent->pRight_= grandpa;
    parent->pParent_ = ggrandpa;
    grandpa->pParent_ = parent;

    //更改 ggrandpa 的指向
    //  3 种情况   ggrandpa 为 head   2  不为head ,但是grandpa为其左子树  3  为其右子树
    if(grandpa == ggrandpa->pLeft_)
      parent = ggrandpa->pLeft_;
    else if(grandpa == ggrandpa ->pRight_)
      parent = ggrandpa->pRight_;
    else 
      parent = ggrandpa->pParent_;
  }

  void rotateR(PNode& pNode)//参数为要被旋转的结点
  {
    PNode parent   = pNode->pParent_;//要旋转节点的父节点
    PNode newHead = pNode->pLeft_;

    pNode->pLeft_ = newHead->right;
    newHead->pRight_ = pNode;
    pNode->pParent_ = newHead;
    newHead->pParent_ = parent;

    if(pNode == parent->pLeft_)
      parent->pLeft_ = newHead;
    else if(pNode == parent ->pRight_)
      parent->pRight_ = newHead;
    else 
      parent ->pParent_ = newHead;


  }
  //左旋
  void rotateL(PNode& pNode)
  {
    PNode  parent  =pNode;
    PNode  cur  = pNode->pRight_;
    PNode grandpa = parent->pParent_;

    parent->pRight_ = cur->pLeft_;
    cur->pLeft_ = parent;
    cur->pParent_ = parent->pParent_;
    parent->pParent_ = cur;

    if(grandpa->pLeft_  == grandpa)
      grandpa->pLeft_ = parent;
    else if(grandpa->pRight_ == grandpa)
      grandpa->pRight_ = parent;
    else 
      grandpa->pParent_ = parent;

  }



  //下边这种写法不好
  void rotateL1(PNode grandpa)
  {
    PNode ggrandpa = grandpa->pParent_;//爷爷的父亲
    PNode parent = grandpa->pLeft_; 
    PNode uncle = grandpa ->pRight_;

    grandpa->pRight_ = parent->pLeft_;
    parent->pLeft_ = grandpa;
    parent->pParent_ = ggrandpa;
    grandpa->pParent_ = parent;

    if(grandpa == ggrandpa->pLeft_)
      parent = ggrandpa->pLeft_;
    else if(grandpa == ggrandpa ->pRight_)
      parent = ggrandpa->pRight_;
    else 
      parent = ggrandpa->pParent_;
  }


  bool Insert(const T& data)
  {
    PNode pRoot = GetRoot();
    //双亲为空
    if(pRoot == nullptr)   //空树
    {
      pRoot = new RBTree (data,RED);

      pRoot ->pParent_ = pHead_;
      pHead_->pParent_ = pRoot;
    }
    else//不是空树 
    {
      //1  像二叉搜索树那样 先插入 ,
      //2 可能进行相应的旋转,调色
      PNode cur = pRoot;
      PNode parent = nullptr;
      while(cur)
      {
        parent = cur;//保存双亲,方便后期插入节点时进行连接
        if(cur->data_ > data)
        {
          cur =cur ->pLeft_ ;             
        }
        else if(cur->data_ < data)
        {
          cur = cur->pRight_;
        }
        else 
        {
          std::cout<<"data 已存在,插入失败"<<std::endl;
          return false; 
        }
      }


      //cur == nullptr  即这里就是应该插入的位置  插入节点 并进行链接
      cur = new PNode (data,RED);
      cur->pParent_ = parent;

      if(cur->data_ < parent->data_)
        cur = parent->pLeft_;
      else 
        cur = parent->pRight_;
      //插入节点完成,  进行判断是否需要 旋转 + 改色 调整


      //1  parent  不存在,即 空树 
      //直接处理跟颜色即可

      //2  parent  存在 ,parent 为黑色
      //直接插入,不需要 调整

      //3  parent 存在(即不是空树)  并且cur父亲为红色,叔叔为红色

      //4  parent  存在 ,叔父不同色,  ----------直线型
      //1  自己是父亲的左子树,  父亲是爷爷的左子树,      
      //2  自己是父亲的右子树,  父亲是爷爷的右子树,   

      //5  parent  存在 ,  叔父不同色  ---------- √型
      //1   ,自己是父亲的右子树,  父亲是爷爷的左子树         
      //2     自己是父亲的左子树,  父亲是爷爷的右子树


      //注意 ,插入的结点一定是红色结点,能走到这里 ,说明不是空树,也不是只有根结点的树,所有 grandpa 必定存在
      while(parent)
      {
        PNode grandpa = parent->pParent_;
        PNode uncle;
        if(parent == grandpa->pLeft_)
          uncle = grandpa->pRight_;
        else  
          uncle = grandpa->pLeft_;


        //3 叔父 同红    --向上调整
        if( parent->color_ == uncle->color_ ) 
        {
          parent->color_ = BLACK;
          uncle->color_  = BLACK;
          grandpa->color_= RED;
          parent = grandpa;
        }

        //4  +  5      叔不存在或者 叔父不同色 
        else if(!uncle || parent->color != uncle->color_) 
        {
          //1  确定形状   -- 左右型,  左左型  右左型,右右型  或者 直线型, √型         
          //2  选择旋转方案
          if(parent == grandpa->pLeft_  && cur == parent->pRight_)  //  √型    将grandpa右旋
          { 
            //先左旋,再右旋
            rotateL(parent);//左旋形成执行行
            rotateR(grandpa);
            //调色
            grandpa->color_ = RED;
            parent->color_ = BLACK;
          }

          else if(parent == grandpa->Right_  && cur == parent->pLeft_)//  √型    parent 左旋
          {
            rotateR(parent);//右旋形成直线型
            rotareL(grandpa);
            // 调色
            grandpa ->color_ = RED;
            cur->color_ = BLACK;
          }
          else if(parent == grandpa->pLeft_ && cur == parent ->pLeft_ )//     直线型   左直线
          {
            rotateR(grandpa);
            parent->color_ = BLACK;
            grandpa ->color_ = RED;
          }

          else                                                       //直线型    右直线
          {
            rotateL(grandpa);
            parent->color = BLACK;
            grandpa->color_ =RED;
          }

        }
      }
    }
    pRoot->color_ = BLACK;
    pHead_->pLeft_ = GetLeftMost();
    pHead_->pRight_ = GetRightMost();

  }




  private: 
  PNode& GetRoot()
  {
    return pHead_->pParent_;
  }
  PNode& GetLeftMost()
  {
    PNode cur = pHead_->pParent_; //获取根节点 
    while(cur)
    {
      if(cur->pLeft_)
      {
        cur = cur->pLeft_;
      }
      else if(cur->pRight_)
      {
        cur = cur->pRight_;
      }
      else 
        return cur;
    }
  }


  PNode& GetRightMost()
  {
    PNode cur = pHead_->pParent_;
    while(cur)
    {
      if(cur->pRight_)
      {
        cur = cur->pRight_;
      }
      else if(cur->pLeft_)
      {
        cur = cur ->pLeft_;
      }
      else 
        return cur;
    }
  }



  //检查是否为红黑树
  bool IsRBTree()
  {

    PNode cur = GetRoot();

    if(! cur) //空树
    {
      std::cout<<"满足"<<std::endl;
      return true;
    }

    else if( cur ->color_ != BLACK )
    {
      std::cout<<"根节点颜色不满足 "<<std::endl;
      return false;
    }
    else 
    {
      //  获取 任意一条路径上黑色叶子个数
      int black_count =0;
      while(cur)
      {
        if(cur && cur->color_ == BLACK)
          ++black_count;

        cur = cur->pLeft_;
      }
      int k = 0;
      return IsEqualpath(black_count, k ,GetRoot());
    }
  }


  bool IsEqualpath(int bk, int k, PNode pRoot)
  {
    // 终止条件
    if(pRoot == nullptr) 
    {
      if(k != bk)
      {
        std::cout<<"存在路径黑色节点不相等的情况"<<std::endl;
        return false;
      }
      return true;
    }

    if( pRoot == BLACK ) 
    {
      ++k;
    }

    // 不是黑色就是v红色结点,红色结点需要判断 是否存在相连通红
    else 
    {
      PNode parent = pRoot->pParent_;
      if(parent  && parent->color_ == RED)
      {
        std::cout<<"存在相邻同为红色的结点,因此不是红黑树"<<std::endl;
        return false;
      }

      return IsEqualpath(bk,k, pRoot->pLeft_)  && 
        IsEqualpath(bk,k ,pRoot->pRight_);
    }
  }

  private:
  PNode pHead_;//红黑树根节点
};
发布了90 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44030580/article/details/104617175