红黑树的插入详解及Javascript实现方法示例


Posted in Javascript onMarch 26, 2018

红黑树的性质

一棵满足以下性质的二叉搜索树是一棵红黑树

  1. 每个结点或是黑色或是红色。
  2. 根结点是黑色的。
  3. 每个叶结点(NIL)是黑色的。
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的。
  5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

性质1和性质2,不用做过多解释。

红黑树的插入详解及Javascript实现方法示例

性质3,每个叶结点(NIL)是黑色的。这里的叶结点并不是指上图中结点1,5,8,15,而是指下图中值为null的结点,它们的颜色为黑色,且是它们父结点的子结点。

红黑树的插入详解及Javascript实现方法示例

性质4,如果一个结点是红色的(图中用白色代替红色),则它的两个子结点都是黑色的,例如结点2,5,8,15。但是,如果某结点的两个子结点都是黑色的,该结点未必是红色的,例如结点1。

性质5,对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。例如,从结点2到其所有后代叶结点的简单路径上,黑色结点的数量都为2;从根结点11到其所有后代叶结点的简单路径上,黑色结点的数量都为3。

这样的一棵树有什么特点呢?

通过对任何一条从根到叶结点的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因为是近似于平衡的。——《算法导论》

由于性质4,红黑树中不会出现两个红色结点相邻的情形。树中最短的可能出现的路径是都是黑色结点的路径,树中最长的可能出现的路径是红色结点和黑色结点交替的路径。再结合性质5,每条路径上均包含相同数目的黑色结点,所以红黑树确保没有一条路径会比其他路径长出2倍。

红黑树的插入

首先以二叉搜索树的方式插入结点,并将其着为红色。如果着为黑色,则会违背性质5,不便调整;如果着为红色,可能会违背性质2或性质4,可以通过相对简单的操作,使其恢复红黑树的性质。

一个结点以二叉搜索树的方式被插入后,可能出现以下几种情况:

情形1

插入结点后,无父结点,结点插入成为根结点,违背性质2,将结点调整为黑色,完成插入。

情形2

插入结点后,其父结点为黑色,没有违背任何性质,不用调整,完成插入。例如下图中插入结点13。

红黑树的插入详解及Javascript实现方法示例

情形3

插入结点后,其父结点为红色,违背了性质4,需要采取一系列的调整。例如下图中插入结点4。

红黑树的插入详解及Javascript实现方法示例

那么一系列的调整是什么呢?

如果插入结点node的父结点father为红色,则结点father必然存在黑色的父结点grandfather,因为如果结点father不存在父结点的话,就是根结点,而根结点是黑色的。那么结点grandfather的另一个子结点,我们可以称之为结点uncle,即结点father的兄弟结点。结点uncle可能为黑色,也可能为红色。

先从最简单的情形分析,因为复杂的情形可以转化为简单的情形,简单的情形就是结点uncle为黑色的情形。

红黑树的插入详解及Javascript实现方法示例

情形3.1

如上图(a)中,情形是这样的,node 为红,father 为红,grandfather 和 uncle 为黑,α,β,θ,ω,η 都是结点对应的子树。假设整棵二叉搜索树中,只有node和father因违背性质4而无法成为正常的红黑树,此时将图(a)调整成图(b),则可以恢复成正常的红黑树。整个调整过程中实际分为两步,旋转和变色。

什么是旋转?

红黑树的插入详解及Javascript实现方法示例

如上图(c)是一棵二叉搜索树的一部分,其中 x, y 是结点,α,β,θ 是对应结点的子树。由图可知,α < x < β < y < θ ,即 α子树中的所有结点都小于x,结点 x都小于 β子树中的所有结点,β子树中的所有结点的值都小于结点 y 的值,结点 y 的值都小于 θ子树中的所有结点。在二叉搜索树中,如果结点y的值比结点x的值大,那么结点x在结点y的左子树中,如图(c);或者结点y在结点x的右子树中,如图(d)。故 α < x < β < y < θ ,也可以用图(d)的结构来表现。这就是旋转,它不会破坏二叉搜索树的性质。

node 为红,father 为红,grandfather 和 uncle 为黑的具体情形一

图(a)中,node 为 father 的左子结点, father 为 grand 的左子结点,node < father < θ < grand < uncle。这种情形中 father < grand,即可以表现为 father 是 grand 的左子树,也可以表现为 grand 是 father 的右子树,故将图(a)中 father 和 grand 旋转,旋转虽然不会破坏二叉搜索树的性质,但是旋转之后,会破坏红黑树的性质,所以还需要调整结点的颜色。

变色

所以图(a)旋转过后,还要将 grand 变为红色,father 变为黑色,变成图(b),完成插入。

node 为红,father 为红,grandfather 和 uncle 为黑的具体情形二

node 为 father 的右子结点, father 为 grand 的右子结点,如下图(e),就是具体情形一的翻转。

红黑树的插入详解及Javascript实现方法示例

即,uncle < grand < θ < father < node ,将图(e)中 father 和 grand 旋转,变色后,变成图(f),完成插入。

node 为红,father 为红,grandfather 和 uncle 为黑的具体情形三

node 为 father 的右子结点, father 为 grand 的左子结点,如下图(m)。

红黑树的插入详解及Javascript实现方法示例

将图(m)中 node 和 father 旋转后,变成图(n),将father看作新的node,就成为了具体情形一,再次旋转,变色后,完成插入。

node 为红,father 为红,grandfather 和 uncle 为黑的具体情形四

node 为 father 的右子结点, father 为 grand 的左子结点,如下图(i),就是具体情形三的翻转。

红黑树的插入详解及Javascript实现方法示例

将图(i)中 node 和 father 旋转后,变成图(j),将father看作新的node,就成为了具体情形二,再次旋转,变色后,完成插入。

情形3.2

node ,father 和 uncle 为红,grandfather 为黑。

红黑树的插入详解及Javascript实现方法示例

如上图(k),不旋转,而是将grand着红,father和uncle着黑,同时将grand作为新的node,进行情形的判断。如果grand作为新的node后,变成了情形2,则插入完成;如果变成了情形3.1,则调整后,插入完成;如果仍是情形3.2,则继续将grand,father和uncle变色,和node结点上移,如果新的node结点没有父节点了,则变成了情形1,将根结点着为黑色,那么插入完成。

综上

node的情形 操作
情形1 node为红,无father 将node重新着色
情形2 node为红,father为黑
情形3.1 node,father为红,grand,uncle为黑 旋转一次或两次,并重新着色
情形3.2 node,father,uncle为红,grand为黑 将father, uncle,grand重新着色, grand作为新的node

代码

// 结点
function Node(value) {
 this.value = value
 this.color = 'red' // 结点的颜色默认为红色
 this.parent = null
 this.left = null
 this.right = null
}

function RedBlackTree() {
 this.root = null
}

RedBlackTree.prototype.insert = function (node) {
 // 以二叉搜索树的方式插入结点
 // 如果根结点不存在,则结点作为根结点
 // 如果结点的值小于node,且结点的右子结点不存在,跳出循环
 // 如果结点的值大于等于node,且结点的左子结点不存在,跳出循环
 if (!this.root) {
 this.root = node
 } else {
 let current = this.root
 while (current[node.value <= current.value ? 'left' : 'right']) {
  current = current[node.value <= current.value ? 'left' : 'right']
 }
 current[node.value <= current.value ? 'left' : 'right'] = node
 node.parent = current
 }
 // 判断情形
 this._fixTree(node)
 return this
}

RedBlackTree.prototype._fixTree = function (node) {
 // 当node.parent不存在时,即为情形1,跳出循环
 // 当node.parent.color === 'black'时,即为情形2,跳出循环
 while (node.parent && node.parent.color !== 'black') {
 // 情形3
 let father = node.parent
 let grand = father.parent
 let uncle = grand[grand.left === father ? 'right' : 'left']
 if (!uncle || uncle.color === 'black') {
  // 叶结点也是黑色的
  // 情形3.1
  let directionFromFatherToNode = father.left === node ? 'left' : 'right'
  let directionFromGrandToFather = grand.left === father ? 'left' : 'right'
  if (directionFromFatherToNode === directionFromGrandToFather) {
  // 具体情形一或二
  // 旋转
  this._rotate(father)
  // 变色
  father.color = 'black'
  grand.color = 'red'
  } else {
  // 具体情形三或四
  // 旋转
  this._rotate(node)
  this._rotate(node)
  // 变色
  node.color = 'black'
  grand.color = 'red'
  }
  break // 完成插入,跳出循环
 } else {
  // 情形3.2
  // 变色
  grand.color = 'red'
  father.color = 'black'
  uncle.color = 'black'
  // 将grand设为新的node
  node = grand
 }
 }

 if (!node.parent) {
 // 如果是情形1
 node.color = 'black'
 this.root = node
 }
}

RedBlackTree.prototype._rotate = function (node) {
 // 旋转 node 和 node.parent
 let y = node.parent
 if (y.right === node) {
 if (y.parent) {
  y.parent[y.parent.left === y ? 'left' : 'right'] = node
 }
 node.parent = y.parent
 if (node.left) {
  node.left.parent = y
 }
 y.right = node.left
 node.left = y
 y.parent = node
 } else {
 if (y.parent) {
  y.parent[y.parent.left === y ? 'left' : 'right'] = node
 }
 node.parent = y.parent
 if (node.right) {
  node.right.parent = y
 }
 y.left = node.right
 node.right = y
 y.parent = node
 }
}

let arr = [11, 2, 14, 1, 7, 15, 5, 8, 4, 16]
let tree = new RedBlackTree()
arr.forEach(i => tree.insert(new Node(i)))
debugger

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
silverlight线程与基于事件驱动javascript引擎(实现轨迹回放功能)
Aug 09 Javascript
jQuery页面滚动浮动层智能定位实例代码
Aug 23 Javascript
javascript处理表单示例(javascript提交表单)
Apr 28 Javascript
jQuery实现下拉加载功能实例代码
Apr 01 Javascript
Bootstrap Fileinput文件上传组件用法详解
May 10 Javascript
JS定时器实现数值从0到10来回变化
Dec 09 Javascript
随机生成10个不重复的0-100的数字(实例讲解)
Aug 16 Javascript
获取本机IP地址的实例(JavaScript / Node.js)
Nov 24 Javascript
nuxt.js 缓存实践
Jun 25 Javascript
详解如何为你的angular app构建一个第三方库
Dec 07 Javascript
javascript实现的时间格式加8小时功能示例
Jun 13 Javascript
微信小程序tab左右滑动切换功能的实现代码
Feb 08 Javascript
js+canvas实现滑动拼图验证码功能
Mar 26 #Javascript
JS从非数组对象转数组的方法小结
Mar 26 #Javascript
深入理解Node module模块
Mar 26 #Javascript
利用Console来Debug的10个高级技巧汇总
Mar 26 #Javascript
关于vuejs中v-if和v-show的区别及v-show不起作用问题
Mar 26 #Javascript
vue中使用iview自定义验证关键词输入框问题及解决方法
Mar 26 #Javascript
Vue中v-show添加表达式的问题(判断是否显示)
Mar 26 #Javascript
You might like
php二分查找二种实现示例
2014/03/12 PHP
wamp服务器访问php非常缓慢的解决过程
2015/07/01 PHP
PHP大神的十大优良习惯
2016/09/14 PHP
深入了解PHP中的Array数组和foreach
2016/11/06 PHP
解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory
2017/02/25 PHP
IE autocomplete internet explorer's autocomplete
2007/06/30 Javascript
改进版通过Json对象实现深复制的方法
2012/10/24 Javascript
Jquery submit()无法提交问题
2013/04/21 Javascript
js实现星星打分效果的方法
2020/07/05 Javascript
jQuery通过deferred对象管理ajax异步
2016/05/20 Javascript
使用递归遍历对象获得value值的实现方法
2016/06/14 Javascript
AngularJS中指令的四种基本形式实例分析
2016/11/22 Javascript
Vue下拉框回显并默认选中随机问题
2018/09/06 Javascript
微信小程序实现单选选项卡切换效果
2020/06/19 Javascript
Vue使用mixin分发组件的可复用功能
2019/09/01 Javascript
JavaScript前端实现压缩图片功能
2020/03/06 Javascript
详解Vue的组件中data选项为什么必须是函数
2020/08/17 Javascript
[03:49]辉夜杯现场龙骑士COSER秀情商“我喜欢芬队!”
2015/12/27 DOTA
[33:09]完美世界DOTA2联赛循环赛 Forest vs DM BO2第二场 10.29
2020/10/29 DOTA
Python实现简单字典树的方法
2016/04/29 Python
Python判断列表是否已排序的各种方法及其性能分析
2016/06/20 Python
python解决字符串倒序输出的问题
2018/06/25 Python
python使用matplotlib画饼状图
2018/09/25 Python
Python3实现爬虫爬取赶集网列表功能【基于request和BeautifulSoup模块】
2018/12/05 Python
python 实现调用子文件下的模块方法
2018/12/07 Python
pygame实现俄罗斯方块游戏(对战篇1)
2019/10/29 Python
python3定位并识别图片验证码实现自动登录功能
2021/01/29 Python
美津浓美国官网:Mizuno美国
2018/08/07 全球购物
意大利香水和化妆品购物网站:Parfimo.it
2019/10/06 全球购物
小学教师自我鉴定
2013/11/07 职场文书
警察思想汇报
2014/01/04 职场文书
元旦促销方案
2014/03/15 职场文书
经营目标责任书
2015/05/08 职场文书
教你解决往mysql数据库中存入汉字报错的方法
2021/05/06 MySQL
php7中停止php-fpm服务的方法详解
2021/05/09 PHP
css 边框添加四个角的实现代码
2021/10/16 HTML / CSS