当前位置: 首页 > news >正文

红黑树(创建 插入 测试验证)

一 红黑树的概念与规则

红黑树的概念 :红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。

  红黑树的性质:

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

 2. 根节点是黑色的

 3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

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

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

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点 个数的两倍?   

这里根据第三条和第四条性质 红黑树理想中最短路径为全黑 数目为  最长路径为一黑一红 数目为最短路径*2  这只是理想状态下  一般是不会造成这种情况  所以最长路径 <= 2*最短路径  

相较于avl树的平衡因子控制 红黑树的颜色控制是更简单的

enum colour 
{red,black
};
template <class K, class V>
struct rbtnode//搜索二叉树不支持修改  中序遍历是有序的
{pair<int, int> _kv;rbtnode<K, V>* _left;rbtnode<K, V>* _right;rbtnode<K, V>* _parent;colour _col;avltnode(const pair<int, int>& kv): _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

template<class K, class V>
class rbTree
{typedef rbtnode<K, V> node;
public:rbTree() = default;
private:node* _root = nullptr;
};

二 插入

   接下来考虑插入的结点默认颜色是红的好还是黑的好 

由于规则四限制每条路径上的黑色节点数目必须保持一致  所以如果插入一个黑色节点就破坏了规则  如果插入一颗红色 如果其父亲是黑色那么就没有破坏规则  如果是红色 就破坏了规则  从这点上考虑  所以我们选择插入节点颜色默认为红色  

bool insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new node(kv);_root->_col = black;return true;}node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;return true;
}

接下来我们具体的分析一下红黑树的插入规则 

红黑树的改变通过观察红黑树结点的父亲 爷爷 叔叔结点  其中叔叔节点对于进行如何的调整非常重要

情况一 abcde都为空 

我们这里观察的是高度 不是颜色  cur是新增节点  对于两个连续的红色节点 会有两种调整方式 一种是旋转 一种是变色  其中我们优先进行变色  但是要具体决定是根据叔叔节点的情况进行选择的

情况1: cur为红,p为红,g为黑,u存在且为红   这里我们要将叔叔和父亲变黑 同时要将爷爷变红

之后继续向上调整  那么为什么要将爷爷变黑呢  因为如果爷爷是子树节点 那么如果只将父亲和叔叔变黑 那么在这个子树局部路径上就会比其他,路径上多一个黑节点 这样就破坏了规则四 、

 情况2:cur位置不是新增结点 cur之前是黑色 cde是一个拥有一个黑色结点的红黑子树  ab分别是一个红色节点  新增节点是在ab的任意位置

根据上图  新增节点之后  父亲和叔叔都变黑 爷爷变红  之后将爷爷当成cur节点 再次进行判断cur的父亲 爷爷 叔叔结点  

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

  情况1  如果u不存在 abcde都为空 cur是新增 

这时无论怎么变颜色 都无法保证路径黑色节点一致 这时就需要用到旋转   对于下图  先对其进行右旋转 之后 将父亲变为黑色 将爷爷节点变为红色 

情况2 如果叔叔存在且为黑   de为空或者为红  c是一个右黑色节点的红黑子树

 cur节点时黑  是由情况一变过来导致变为红色的  ab是一个红色节点  新增节点在ab任意位置上

情况3 进行左单旋之后变色

情况4 进行左旋之后右旋之后进行变色

情况5 进行右旋之后左旋之后进行变色

二  接下来我们写实现代码 

insert插入

bool insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new node(kv);_root->_col = black;return true;}node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;return true;
}

1.这就是插入一个搜索二叉树节点的代码  如果是插入根节点 则设置为黑色 如果出入的是其他节点就设置为红色  同时按照比较值大小插入在对应的位置  

2.接下来就进行对于颜色的判断以及维护 

bool insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new node(kv);_root->_col = black;return true;}node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;while (parent&&parent ->_col == red){node* grandfather = parent->_parent;if (parent == grandfather->_left){node* uncle = grandfather->_right;if (uncle->_col == red){uncle->_col = parent->_col = black;grandfather->_col = red;cur = grandfather;parent = grandfather->_parent;}}}_root->_col = black;return true;
}

这里我们首先对parent的颜色进行判断 如果是红色 就说明需要进行维护  如果是黑色就结束 结束维护后将根节点再次设置为黑色 这里是为了防止维护颜色时可能会将根节点变为红色  

情况一 :这里首先对uncle如果是红色 那么只需要变色维护 将叔叔节点和父亲节点变为黑色 将爷爷节点变为红色  之后将cur节点变为爷爷节点 parent变为爷爷的父节点  之后再次进行循环判断  

情况二 如果判断叔叔节点 不存在   那么就需要进行旋转 这里也需要判断是单旋转还是双旋转 

  这里如果三个节点时直线型 那么就是单旋转之后将父节点变为黑色将爷爷节点变为红色         如果三个节点是折线型那么就双旋转  之后将cur节点变为黑色 将爷爷节点变为红色       

情况三 如果判断叔叔节点存在且为黑色 这里的情况不是直接产生  这里是由于情况一产生并且维护后继续第二次维护cur节点的时候会发生的情况   这里也是分单旋转和双旋转  也是通过cur parent grandfather  三个节点时直线型 则为单旋转如果是折线型就是双选转    单旋转完成后将父节点变为黑色将爷爷节点变为红色    双旋转之后将cur节点变为黑色 将爷爷节点变为红色     

这里我们将情况二和情况三归为一种情况

bool insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new node(kv);_root->_col = black;return true;}node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;while (parent&&parent ->_col == red){node* grandfather = parent->_parent;if (parent == grandfather->_left){node* uncle = grandfather->_right;if (uncle&&uncle->_col == red){uncle->_col = parent->_col = black;grandfather->_col = red;cur = grandfather;parent = grandfather->_parent;}else//叔叔不存在或叔叔存在且为黑{if (cur == parent->_left){rotater(grandfather);parent->_col = black;grandfather->_col =red;}else{rotatel(parent);rotater(grandfather);cur->_col = black;grandfather->_col = red;}break;}}else {node* uncle = grandfather->_left;if (uncle&&uncle->_col == red){uncle->_col = parent->_col = black;grandfather->_col = red;cur = grandfather;parent = grandfather->_parent;}else//叔叔不存在或叔叔存在且为黑{if (cur == parent->_right){rotatel(grandfather);parent->_col = black;grandfather->_col = red;}else{rotater(parent);rotatel(grandfather);cur->_col = black;grandfather->_col = red;}break;}}}_root->_col = black;return true;
}

   

三 接下来我们对insert代码进行测试

void testrbtree()
{rbTree<int, int> t;int a[] = { 16,3,7,11,9,26,18,14,15 };int b[] = { 4,2,6,1,3,5,15,7,16,14 };for (auto e : a){t.insert({ e,e });}t.inorder();}

这里只能证明它是一个搜索树 平衡还需要另外证明

bool isbalance()
{if (_root == nullptr)return true;if (_root->_col == red){return false;}// 参考值int refNum = 0;node* cur = _root;while (cur){if (cur->_col == black){++refNum;}cur = cur->_left;}return check(_root, 0, refNum);
}
private:bool check(node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}if (root->_col == red && root->_parent->_col == red){cout << root->_kv.first << "存在连续的红色节点" << endl;return false;}if (root->_col == black){blackNum++;}return check(root->_left, blackNum, refNum)&& check(root->_right, blackNum, refNum);} 

 这里的首先计算一路的黑色节点数量用来作为参考值  同时进入check函数来进行检查  在check函数中通过递归来检查整个树的每条路   单检查到空节点的时候开始进行检查  

void testrbtree()
{rbTree<int, int> t;int a[] = { 16,3,7,11,9,26,18,14,15 };int b[] = { 4,2,6,1,3,5,15,7,16,14 };for (auto e : b){t.insert({ e,e });}t.inorder();cout << t.isbalance()<<endl;}

四  红黑树与AVL树的比较

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


http://www.mrgr.cn/news/56419.html

相关文章:

  • 技术经济学·技术经济分析指标体系与基本原则
  • 【C++刷题】力扣-#346-数据流中的移动平均值
  • OAK相机的标定流程更新与优化通知
  • 电感的学习
  • Unity中通过给定的顶点数组生成凸面体的方法参考
  • 第四届应用力学与先进材料国际学术会议
  • 深入了解Java
  • 力扣 困难 52.N皇后II
  • <a-table>行数据增加点击事件并获取点击行的数据+自定义button按事件
  • MySQL之CRUD(下)
  • 中间件之MQ-Kafka
  • sql数据库的命令行操作(修改表)
  • Leetcode—1242. 多线程网页爬虫【中等】Plus(多线程)
  • C语言初阶小练习4(不用临时变量交换数值)
  • dolphinscheduler创建工作流及工作流中DataX的使用(简单操作)
  • TikTok账号被限流怎么解决?
  • 【二】企业级JavaScript开发之代码编辑器
  • 什么是表单数据
  • 群晖通过 Docker 安装 Gitea
  • 两个线程交替打印数字
  • 鸿蒙开发:两个重磅更新,鸿蒙版微信要来了!
  • Java学习Day50:唤醒八戒(Excel相关)
  • 中间件之Seata
  • Python酷库之旅-第三方库Pandas(160)
  • Linux基础命令(入门)
  • Java框架之MyBatis Plus