手撕红黑树

简介: 手撕红黑树

一、概念

红黑树,是一种二叉搜索树。但在每个结点上增加一个存储位表示结点的颜色,可以是Red或

Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路

径会比其他路径长出两倍,因而是接近平衡的。


性质:

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

2. 根节点是黑色的

3. 若一个节点是红色的,则它的两个孩子结点是黑色的(即树中没有连续的红色结点)

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(即每条路径上黑色结点数量相等)

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点NIF)


二、红黑树的插入操作

红黑树的插入操作大致可以分成两步:


第一步: 按照二叉搜索树的规则插入新节点

bool insert(const pair<K, V>& kv) {
    if (_root == nullptr) {
      _root = new TreeNode(kv);
      _root->_color = BLACK;
      return true;
    }
    TreeNode* parent = nullptr;
    TreeNode* cur = _root;
    while (cur != nullptr) {
      if (kv.first > cur->_data.first) {
        parent = cur;
        cur = cur->_right;
      }
      else if (kv.first < cur->_data.first) {
        parent = cur;
        cur = cur->_left;
      }
      else return false;
    }
    cur = new TreeNode(kv);
    cur->_color = RED;
    if (kv.first > parent->_data.first) {
      parent->_right = cur;
    }
    else { //kv.first < parent->_data.first)
      parent->_left = cur;
    }
    cur->_parent = parent;
    //………………
  }

第二步: 插入后检测性质是否造到破坏,若遭到破坏则进行调整

新节点的默认颜色是红色,若其双亲结点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入结点的双亲结点颜色为红色时,就出现了连续的红色结点,此时需要对红黑树分情况来讨论:


情况一: cur为红,parent为红,grandfather为黑,uncle存在且为红

29c710ed776e45e3a899c9c0273ec2f2.png


if (uncle != nullptr && uncle->_color == RED) {
    parent->_color = uncle->_color = BLACK;
  grandfather->_color = RED;
  cur = grandfather;
  parent = cur->_parent;
}

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

uncle的情况有两种:


1.若uncle结点不存在时,cur结点一定是新增结点。若cur不是新增结点,则cur和parent之间一定有一个黑色结点。这不满足性质4:每条路径上黑色结点的个数相同。


2.若uncle存在且为黑色,那么cur原来的颜色一定为黑色。看到cur结点是红色,是因为cur的子树在调整的过程中将cur的颜色从黑色改变为红色。


ed5af71a5b17439ead3bef43e6e4baa6.png


//右单旋 + 变色
if (cur == parent->_left) {
  rotate_right(grandfather);
  grandfather->_color = RED;
  parent->_color = BLACK;
}
//左单旋 + 变色
if (cur == parent->_right) {
  rotate_left(grandfather);
  grandfather->_color = RED;
  parent->_color = BLACK;
}

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

8d9372e1817f460290d068014dd532fc.png


//左右双旋 + 变色
else {//cur == parent->_right
  rotate_left(parent);
    rotate_right(grandfather);
  cur->_color = BLACK;
  grandfather->_color = RED;
}
//右左双旋 + 变色
else {//cur == parent->_left
  rotate_right(parent);
  rotate_left(grandfather);
  cur->_color = BLACK;
  grandfather->_color = RED;
}


三、红黑树的验证

红黑树的检测分为两步:


检测其是否满足二叉搜索树

使用中序遍历判断其是否有序即可,这里不做过多解释


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

bool IsBalance() {
  //空树也是红黑树
  if (_root == nullptr) return true;
  //根结点是黑色的
  if (_root->_color != BLACK) return false;
  int benchmark = 0;//基准值
  return _IsBalance(_root, 0, benchmark);
}
bool _IsBalance(TreeNode* root, int blackNum, int& benchmark) {
  if (root == nullptr) {
    if (benchmark == 0) {
      benchmark = blackNum;//将第一条路径的blackNum设为基准值
        return true;
    }
    else {
      return blackNum == benchmark;
    }
  }
  if (root->_color == BLACK) ++blackNum;
  if (root->_color == RED && root->_parent->_color == RED) return false;
    //逻辑短路,若root结点为红色,其就不可能为根结点,一定有parent结点
  return _IsBalance(root->_left, blackNum, benchmark) && 
            _IsBalance(root->_right, blackNum, benchmark);
}

四、完整代码

#include<iostream>
#include<cassert>
using std::pair;
using std::make_pair;
using std::cout;
using std::cout;
using std::endl;
enum Color { RED,BLACK };
template<class K,class V>
struct RedBlackTreeNode {
  RedBlackTreeNode(const pair<K, V>& kv) :
        _parent(nullptr), 
        _left(nullptr), 
        _right(nullptr), 
        _data(kv),
        _color(RED){}
  RedBlackTreeNode<K, V>* _parent;
  RedBlackTreeNode<K, V>* _left;
  RedBlackTreeNode<K, V>* _right;
  pair<K, V> _data;
  Color _color;
};
template<class K,class V>
class RedBlackTree 
{
  typedef RedBlackTreeNode<K, V> TreeNode;
public:
  bool insert(const pair<K, V>& kv) {
    if (_root == nullptr) {
      _root = new TreeNode(kv);
      _root->_color = BLACK;
      return true;
    }
    TreeNode* parent = nullptr;
    TreeNode* cur = _root;
    while (cur != nullptr) {
      if (kv.first > cur->_data.first) {
        parent = cur;
        cur = cur->_right;
      }
      else if (kv.first < cur->_data.first) {
        parent = cur;
        cur = cur->_left;
      }
      else return false;
    }
    cur = new TreeNode(kv);
    cur->_color = RED;
    if (kv.first > parent->_data.first) {
      parent->_right = cur;
    }
    else { //kv.first < parent->_data.first)
      parent->_left = cur;
    }
    cur->_parent = parent;
    while (parent && parent->_color == RED)
    {
      TreeNode* grandfather = parent->_parent;
      assert(grandfather != nullptr);
            //当parent结点为红时,grandfather结点必不为空(根结点为黑)
      assert(grandfather->_color == BLACK);
            //当parent结点为红时,grandfather结点必为黑色(否则违反性质,出现连续的红色结点)
      if (parent == grandfather->_left) {
        TreeNode* uncle = grandfather->_right;
        if (uncle != nullptr && uncle->_color == RED) {
          parent->_color = uncle->_color = BLACK;
          grandfather->_color = RED;
          cur = grandfather;
          parent = cur->_parent;
        }
        else {//uncle不存在或者为黑
          //右单旋 + 变色
          if (cur == parent->_left) {
            rotate_right(grandfather);
            grandfather->_color = RED;
            parent->_color = BLACK;
          }
          //左右双旋 + 变色
          else {//cur == parent->_right
            rotate_left(parent);
            rotate_right(grandfather);
            cur->_color = BLACK;
            grandfather->_color = RED;
          }
          break;
        }
      }
      else {//parent == grandfather->_right
        TreeNode* uncle = grandfather->_left;
        if (uncle != nullptr && uncle->_color == RED) {
          parent->_color = uncle->_color = BLACK;
          grandfather->_color = RED;
          cur = grandfather;
          parent = cur->_parent;
        }
        else {//uncle不存在或者为黑
          //左单旋 + 变色
          if (cur == parent->_right) {
            rotate_left(grandfather);
            grandfather->_color = RED;
            parent->_color = BLACK;
          }
          //右左双旋 + 变色
          else {//cur == parent->_left
            rotate_right(parent);
            rotate_left(grandfather);
            cur->_color = BLACK;
            grandfather->_color = RED;
          }
          break;
        }
      }
    }
    _root->_color = BLACK;
    return true;
  }
  void inorder() {
    _inorder(_root);
  }
  bool IsBalance() {
    //空树也是红黑树
    if (_root == nullptr) return true;
    //根结点是黑色的
    if (_root->_color != BLACK) return false;
    int benchmark = 0;//基准值
    return _IsBalance(_root, 0, benchmark);
  }
private:
  void _inorder(TreeNode* root) {
    if (root == nullptr) {
      return;
    }
    _inorder(root->_left);
    cout << root->_data.first << ":" << root->_data.second << " ";
    _inorder(root->_right);
  }
  bool _IsBalance(TreeNode* root, int blackNum, int& benchmark) {
    if (root == nullptr) {
      if (benchmark == 0) {
        benchmark = blackNum;
        return true;
      }
      else {
        return blackNum == benchmark;
      }
    }
    if (root->_color == BLACK) ++blackNum;
    if (root->_color == RED && root->_parent->_color == RED) return false;
        //逻辑短路,若root结点为红色,其就不可能为根结点,一定有parent结点
    return _IsBalance(root->_left, blackNum, benchmark) && 
                _IsBalance(root->_right, blackNum, benchmark);
  }
  void rotate_left(TreeNode* parent) {
    TreeNode* subR = parent->_right;
    TreeNode* subRL = subR->_left;
    TreeNode* pparent = parent->_parent;
    parent->_right = subRL;
    if (subRL != nullptr) subRL->_parent = parent;
    subR->_left = parent;
    parent->_parent = subR;
    //解决根结点变换带来的问题
    if (_root == parent) {
      _root = subR;
      subR->_parent = nullptr;
    }
    else {
      if (pparent->_left == parent) pparent->_left = subR;
      else pparent->_right = subR;
      subR->_parent = pparent;
    }
  }
  void rotate_right(TreeNode* parent) {
    TreeNode* subL = parent->_left;
    TreeNode* subLR = subL->_right;
    TreeNode* pparent = parent->_parent;
    parent->_left = subLR;
    if (subLR != nullptr) subLR->_parent = parent;
    subL->_right = parent;
    parent->_parent = subL;
    if (_root == parent) {
      _root = subL;
      subL->_parent = nullptr;
    }
    else {
      if (pparent->_left == parent) pparent->_left = subL;
      else pparent->_right = subL;
      subL->_parent = pparent;
    }
  }
private:
  TreeNode* _root = nullptr;
};
void RBTreeTest() {
  size_t N = 10000;
  srand((unsigned)time(NULL));
  RedBlackTree<int, int> t;
  for (size_t i = 0; i < N; ++i) {
    int x = rand();
    //cout << "insert:" << x << ":" << i << endl;
    t.insert(make_pair(x, i));
  }
  t.inorder();
  cout << t.IsBalance() << endl;
}
int main() 
{
  RBTreeTest();
  return 0;
}


五、红黑树与AVL树的比较

与AVL树的平衡 (左右高度差不超过1) 相比,红黑树的平衡(没有一条路径会比其他路径长出两倍)并没有那么严格。所以两者在插入或删除相同数据时,红黑树需要旋转调整的次数更少,这使得红黑树的性能略高于AVL树。

可是AVL树更加平衡,查找数据所需的次数不是更加少吗?在AVL树与红黑树中进行数据的查找都十分快捷(譬如在查找100万数据中进行查找只需大概20次),对于CPU从时间上来说并不会造成什么负担。

总的来说,AVL树更适用于插入删除不频繁,只对查找要求较高的场景; 红黑树相较于AVL树更适应对插入、删除、查找要求都较高的场景,红黑树在实际中运用更加广泛。


目录
相关文章
|
前端开发 JavaScript Java
计算机Java项目|基于web的铁路订票管理系统
计算机Java项目|基于web的铁路订票管理系统
202 1
|
C语言
C语言判断逻辑的高阶用法
在C语言中,高级的判断逻辑技巧能显著提升代码的可读性、灵活性和效率。本文介绍了六种常见方法:1) 函数指针,如回调机制;2) 逻辑运算符组合,实现复杂条件判断;3) 宏定义简化逻辑;4) 结构体与联合体组织复杂数据;5) 递归与分治法处理树形结构;6) 状态机管理状态转换。通过这些方法,可以更高效地管理和实现复杂的逻辑判断,使代码更加清晰易懂。
427 88
|
机器学习/深度学习 人工智能 自然语言处理
【2024泰迪杯】C 题:竞赛论文的辅助自动评阅 问题分析及Python 代码实现
本文介绍了2024泰迪杯C题“竞赛论文的辅助自动评阅”的问题分析和Python代码实现,涵盖了论文质量特征构造、自动评分模型建立以及如何利用自然语言处理技术和大语言模型进行论文自动评阅的方法。
253 2
【2024泰迪杯】C 题:竞赛论文的辅助自动评阅 问题分析及Python 代码实现
|
JavaScript 数据安全/隐私保护
如何在Vue组件中调用封装好的外部js文件方法
这篇文章介绍了如何在Vue组件中调用封装好的外部js文件方法,包括在Vue项目中全局引入外部js文件,并在组件中通过this.$myMethod()的方式调用外部js文件中定义的方法。
如何在Vue组件中调用封装好的外部js文件方法
|
XML JavaScript 前端开发
如何优雅地使用 Stack Overflow?
如何优雅地使用 Stack Overflow?
1653 0
|
数据挖掘 BI API
简单了解CRM与SaaS系统
本文介绍了CRM(客户关系管理系统)和SaaS(软件即服务)的概念、应用场景、两者之间的关系以及CRM接口的作用和设置流程,强调了SaaS模式为CRM系统提供了灵活、便捷、经济高效的使用方式,以及CRM接口在数据集成、自动化流程、功能扩展和数据分析方面的重要性。
368 0
|
消息中间件 存储 NoSQL
Redis 从入门到精通之Redis 订阅与发布
Redis 是一个支持发布/订阅模式的高性能内存数据库,支持订阅频道和模式。在 Redis 中,客户端可以订阅一个或多个频道或模式,然后接收发布到这些频道或模式的消息。下面将介绍 Redis 中订阅与发布相关的命令和操作
627 63
Redis 从入门到精通之Redis 订阅与发布
|
存储 算法 数据可视化
【贪心算法经典应用】活动选择详解 python
【贪心算法经典应用】活动选择详解 python
|
消息中间件 负载均衡 算法
【消息中间件】RocketMQ消息发送-请求与响应
前面的文章介绍了,RocketMQ的搭建,以及RocketMQ的NameServer,接下来我们配合着官方提供的demo,进行实际的消息发送学习,主要学习发送方式、发送参数的含义,以及发送中的一些问题
|
关系型数据库 MySQL
【面试题精讲】MySQL-wait_timeout参数
【面试题精讲】MySQL-wait_timeout参数