有趣的算法(八) ——红黑树插入算法
有趣的算法(八)——红黑树插入算法
(原创内容,转载请注明来源,谢谢)
一、概述
红黑树是一种二叉平衡查找树。二叉查找树是二叉树,且树的根节点会比左节点大、比右节点小。
1)二叉查找树
二叉查找树对于数字比较大小,具有重要意义。由于其左子节点都比根节点小,右子节点都比根节点大,要查找一个数是否在其中,或者在某个位置,会变得很容易。
从根节点出发,如果待查数据比根节点小,则往根节点的左子树去查找;反之从右子树查找;如果值和某个节点一样,表示找到;如果到某个节点,其没有子节点,而还没有匹配,则表示数据不存在。
二叉查找树相当于将一组数据排好序,并且实现二分查找的方式,因此速度非常快。二叉查找树详细信息不具体描述,可以上网查看。
2)红黑树
二叉查找树有个固有问题,是其有可能出现树的高度太高,则树不平衡。可能出现某些值存在于树的非常低层次的节点,导致最终效率很低。
红黑树与其不同的地方在于,其是平衡的,通过颜色来进行平衡。
现有以下规定:
1)只有左节点可以是红色。
2)如果左节点是红色,则左节点的子节点不能是红色。
3)如果左右节点都出现红色,则会都变成黑色。
当发生不平衡,红黑树就通过规定的机制左旋、右旋或改变颜色,以达到平衡,使得树的层级尽量低。
情况如下图所示:
二、红黑树详解
在红黑树中插入节点,也是通过查找的方式,在找不到节点的地方,进行插入数据。如果找到某个节点,则修改节点的值。
新插入的节点,一开始默认都是红色。为了便于清晰概念,现规定,节点颜色是红色,指的是节点与其父节点的连接是红色的。
当插入数据后,会发生几种情况 :
1)插入节点后,红黑树按照规定正常排布。
2)插入节点后,不正常排布,出现不符合红黑树的情况。
异常情况处理如下。
1、左旋
当从右侧插入节点后,节点是红色的,则需要左旋。左旋的核心在于节点互换和颜色转换,核心代码如下:
//节点互换
NodeleftNode = headNode.right;
headNode.right= leftNode.left;
leftNode.left= headNode;
//颜色转换
leftNode.color= headNode.color;
headNode.color= RED;
步骤为:
1)将父节点(假设为r)的右节点(即刚插入的节点,加上为a)指向a的左节点。
2)将a的左节点指向r。
3)给a赋予r的颜色,给r赋予红色(相当于新插入节点)。
2、右旋
右旋是当节点的左子节点是红色,且左子节点的左子节点还是红色时,需要调整的情况。其核心即将左子节点调整到中心,而将中心节点变为左子节点的右子节点。
核心代码如下:
NoderightNode = headNode.left;
headNode.left= rightNode.right;
rightNode.right= headNode;
rightNode.color = headNode.color;
headNode.color= RED;
rightNode.N= headNode.N;
左旋和右旋如下图所示:
3、颜色调整
当出现左右节点都是红色时,则将左右节点都置为黑色。且将父节点置为红色。核心代码如下:
headNode.color= RED;
headNode.left.color= BLACK;
headNode.right.color= BLACK;
4、调整顺序
插入节点后,是按照上述顺序逐步调整的。
1)当插入节点后,如果节点位于右边,则可能需要左旋;如果位于节点左边,则可能需要右旋。
2)左旋或右旋后,有可能两个节点都是红色,则还需要颜色调整。
5、获取结果
节点排好序后,通过中序遍历的方式获取结果。关于中序遍历,以前的文章中已经讲过。中序遍历的核心,在于先遍历左节点、再遍历中间节点、最后遍历右节点,则可以实现将结果从小到大进行排列。
6、插入节点详解
1)如果根节点是空,则创建根节点。
if(null ==headNode) return new Node(key, value, 1,RED);
2)比较要插入的节点和当前节点,如果小则比较右节点,如果大则比较左节点,这是通过递归的方式进行比较。如果一致则直接更新节点。
intcmp = key.compareTo(headNode.key);
if(0> cmp) headNode.left = addNode(headNode.left, key, value);
else if(0 < cmp) headNode.right = addNode(headNode.right, key,value);
elseheadNode.value = value;
3)插入节点后,根据实际情况,判断是否需要旋转或者颜色调整。
if(isRed(headNode.right)&& !isRed(headNode.left)) headNode = rotateLeft(headNode);
if(isRed(headNode.left)&& isRed(headNode.left.left)) headNode = rotateRight(headNode);
if(isRed(headNode.right)&& isRed(headNode.left)) flipColor(headNode);
调整概括如下:
7、其他功能
1)查找最小节点
递归遍历,取到最左边的节点即可。同理,最大节点即最右边节点。
2)节点子节点的数量
每次旋转、插入都会进行调整,以更新子节点数量。
三、编程实现(java)
public class RedBlackBSTService<Keyextends Comparable<Key>, Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
Key key;//节点键
Value value;//节点值
Node left, right;//节点左右节点
int N;//节点子节点数量
boolean color;//节点与其父节点连接的颜色
Node(Key key, Value value, int N, boolean color){
this.key = key;
this.value = value;
this.N = N;
this.color = color;
}
}
private Node root;
//查看节点是否是红色(空节点默认是黑色)
privateboolean isRed(Node x) {
return null != x && x.color == RED;
}
//查看节点子节点数目
private int size() {
return size(root);
}
private int size(Node h) {
if (h == null) return 0;
return h.N;
}
private int getTotalSize(Node headNode){
return 1 + size(headNode.left) + size(headNode.right);
}
//获取树的最小、最大节点
public Key getMinKey(){
return minKey(root).key;
}
public Key getMaxKey(){
return maxKey(root).key;
}
private Node minKey(Node node){
if(null == node.left) return node;
return minKey(node.left);
}
private Node maxKey(Node node){
if(null == node.right) return node;
return maxKey(node.right);
}
//左旋(当节点与其右子节点连接是红色时)
private Node rotateLeft(Node headNode){
//节点互换
Node leftNode = headNode.right;
headNode.right = leftNode.left;
leftNode.left = headNode;
//颜色转换
leftNode.color = headNode.color;
headNode.color = RED;
//节点数量重新获取
leftNode.N = headNode.N;
headNode.N = getTotalSize(headNode);
return leftNode;
}
//右旋(当节点与父节点连线是红色,且与其左子节点连线也是红色时)
private Node rotateRight(Node headNode){
Node rightNode = headNode.left;
headNode.left = rightNode.right;
rightNode.right = headNode;
rightNode.color = headNode.color;
headNode.color = RED;
rightNode.N = headNode.N;
headNode.N = getTotalSize(headNode);
return rightNode;
}
//颜色调整(当节点与其左右子节点连线都是红色时)
private void flipColor(Node headNode){
headNode.color = RED;
headNode.left.color = BLACK;
headNode.right.color = BLACK;
}
//插入节点
private Node addNode(Node headNode, Key key, Value value){
//空时新建根节点
if(null == headNode) return newNode(key, value, 1, RED);
//比较待插入节点和当前节点的key
int cmp = key.compareTo(headNode.key);
if(0 > cmp) headNode.left = addNode(headNode.left, key, value);
else if(0 < cmp) headNode.right = addNode(headNode.right, key,value);
else headNode.value = value;
//根据插入后的情况判断是否需要旋转或颜色调整
if(isRed(headNode.right) && !isRed(headNode.left)) headNode =rotateLeft(headNode);
if(isRed(headNode.left) && isRed(headNode.left.left)) headNode =rotateRight(headNode);
if(isRed(headNode.right) && isRed(headNode.left))flipColor(headNode);
//节点大小重新获取
headNode.N = getTotalSize(headNode);
return headNode;
}
public void addNode(Key key, Value value){
root = addNode(root, key, value);
root.color = BLACK;
}
public Stack<Value> getSortedNodes(){
if(null == root) return null;
Stack<Node> nodeList = new Stack<>();
Stack<Value> res = new Stack<>();
Node centerNode = root;
while(!nodeList.empty() || null != centerNode){
while (null != centerNode){
nodeList.push(centerNode);
centerNode = centerNode.left;
}
centerNode = nodeList.pop();
res.push(centerNode.value);
centerNode = centerNode.right;
}
return res;
}
}
四、结语
本文只实现了红黑树的查找和插入,没有实现红黑树的删除,删除功能更加复杂,后续我会继续学习并实现。
完整代码见我的github项目,路径:
https://github.com/linhxx/taskmanagement.git
另外,近期laravel环境我已经搭建好,计划这个月实现laravel+vue的小项目,届时也将发到github。
——written by linhxx 2017.10.14
- Android:StatFs类 获取系统/sdcard存储空间信息
- 数据挖掘干货
- 高效 Mac 人士必备:实现工作/家庭间网络环境切换的自动化
- android中AVD的使用
- ASP.NET MVC 2示例Tailspin Travel UI层分析
- CSS 命名之Dialog, Modal, Popup, Popover, Lightbox 等的区别
- Eclipse JAVA文件注释乱码
- 2018年小程序的红利趋势预测,懂的来……或许你将成为下个富翁
- VUE 入门基础(6)
- 五年换4高管,6000员工裁95%剩300人,王健林为何抛弃万达网科?
- Android Permission中英对照
- 你知道人脸识别技术是如何实现的吗?
- WordPress REST API 定制化输出
- ASP.NET MVC的Action Filter
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android底部导航栏的三种风格实现
- Android Studio3.2中导出jar包的过程详解
- Android自定义view实现标签栏功能(只支持固定两个标签)
- python 爬虫之selenium可视化爬虫
- Android Studio3.6.+ 插件搜索不到终极解决方案(图文详解)
- Spring 整合 SpringDataRedis
- Android 实现可任意拖动的悬浮窗功能(类似悬浮球)
- Android仿抖音右滑清屏左滑列表功能的实现代码
- 利用织梦CMS0day注入漏洞渗透测试
- nodejs中追加内容到文件
- android实现滚动文本效果
- Android实现View的拖拽
- 诊断日志知多少 | DiagnosticSource 在.NET上的应用
- Android Studio 4.0新特性及升级异常问题的解决方案
- Android Studio 4.0 正式发布在Ubuntu 20.04中安装的方法