红黑树的插入及Javascript实现方法详解
本文主要和大家分享红黑树的插入及Javascript实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,希望能帮助到大家。
红黑树的性质
一棵满足以下性质的二叉搜索树是一棵红黑树
每个结点或是黑色或是红色。
根结点是黑色的。
每个叶结点(NIL)是黑色的。
如果一个结点是红色的,则它的两个子结点都是黑色的。
对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
性质1和性质2,不用做过多解释。
性质3,每个叶结点(NIL)是黑色的。这里的叶结点并不是指上图中结点1,5,8,15,而是指下图中值为null的结点,它们的颜色为黑色,且是它们父结点的子结点。
性质4,如果一个结点是红色的(图中用白色代替红色),则它的两个子结点都是黑色的,例如结点2,5,8,15。但是,如果某结点的两个子结点都是黑色的,该结点未必是红色的,例如结点1。
性质5,对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。例如,从结点2到其所有后代叶结点的简单路径上,黑色结点的数量都为2;从根结点11到其所有后代叶结点的简单路径上,黑色结点的数量都为3。
这样的一棵树有什么特点呢?
通过对任何一条从根到叶结点的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因为是近似于平衡的。——《算法导论》
由于性质4,红黑树中不会出现两个红色结点相邻的情形。树中最短的可能出现的路径是都是黑色结点的路径,树中最长的可能出现的路径是红色结点和黑色结点交替的路径。再结合性质5,每条路径上均包含相同数目的黑色结点,所以红黑树确保没有一条路径会比其他路径长出2倍。
红黑树的插入
首先以二叉搜索树的方式插入结点,并将其着为红色。如果着为黑色,则会违背性质5,不便调整;如果着为红色,可能会违背性质2或性质4,可以通过相对简单的操作,使其恢复红黑树的性质。
一个结点以二叉搜索树的方式被插入后,可能出现以下几种情况:
情形1
插入结点后,无父结点,结点插入成为根结点,违背性质2,将结点调整为黑色,完成插入。
情形2
插入结点后,其父结点为黑色,没有违背任何性质,不用调整,完成插入。例如下图中插入结点13。
情形3
插入结点后,其父结点为红色,违背了性质4,需要采取一系列的调整。例如下图中插入结点4。
那么一系列的调整是什么呢?
如果插入结点node的父结点father为红色,则结点father必然存在黑色的父结点grandfather,因为如果结点father不存在父结点的话,就是根结点,而根结点是黑色的。那么结点grandfather的另一个子结点,我们可以称之为结点uncle,即结点father的兄弟结点。结点uncle可能为黑色,也可能为红色。
先从最简单的情形分析,因为复杂的情形可以转化为简单的情形,简单的情形就是结点uncle为黑色的情形。
情形3.1
如上图(a)中,情形是这样的,node 为红,father 为红,grandfather 和 uncle 为黑,α,β,θ,ω,η 都是结点对应的子树。假设整棵二叉搜索树中,只有node和father因违背性质4而无法成为正常的红黑树,此时将图(a)调整成图(b),则可以恢复成正常的红黑树。整个调整过程中实际分为两步,旋转和变色。
什么是旋转?
如上图(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),就是具体情形一的翻转。
即,uncle < grand < θ < father < node ,将图(e)中 father 和 grand 旋转,变色后,变成图(f),完成插入。
node 为红,father 为红,grandfather 和 uncle 为黑的具体情形三
node 为 father 的右子结点, father 为 grand 的左子结点,如下图(m)。
将图(m)中 node 和 father 旋转后,变成图(n),将father看作新的node,就成为了具体情形一,再次旋转,变色后,完成插入。
node 为红,father 为红,grandfather 和 uncle 为黑的具体情形四
node 为 father 的右子结点, father 为 grand 的左子结点,如下图(i),就是具体情形三的翻转。
将图(i)中 node 和 father 旋转后,变成图(j),将father看作新的node,就成为了具体情形二,再次旋转,变色后,完成插入。
情形3.2
node ,father 和 uncle 为红,grandfather 为黑。
如上图(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
相关推荐:
nginx学习九 高级数据结构之红黑树ngx_rbtree_t
以上是红黑树的插入及Javascript实现方法详解的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Windows操作系统是全球最流行的操作系统之一,其新版本Win11备受瞩目。在Win11系统中,管理员权限的获取是一个重要的操作,管理员权限可以让用户对系统进行更多的操作和设置。本文将详细介绍在Win11系统中如何获取管理员权限,以及如何有效地管理权限。在Win11系统中,管理员权限分为本地管理员和域管理员两种。本地管理员是指具有对本地计算机的完全管理权限

OracleSQL中的除法运算详解在OracleSQL中,除法运算是一种常见且重要的数学运算操作,用于计算两个数相除的结果。除法在数据库查询中经常用到,因此了解OracleSQL中的除法运算及其用法是数据库开发人员必备的技能之一。本文将详细讨论OracleSQL中除法运算的相关知识,并提供具体的代码示例供读者参考。一、OracleSQL中的除法运算

人脸检测识别技术已经是一个比较成熟且应用广泛的技术。而目前最为广泛的互联网应用语言非JS莫属,在Web前端实现人脸检测识别相比后端的人脸识别有优势也有弱势。优势包括减少网络交互、实时识别,大大缩短了用户等待时间,提高了用户体验;弱势是:受到模型大小限制,其中准确率也有限。如何在web端使用js实现人脸检测呢?为了实现Web端人脸识别,需要熟悉相关的编程语言和技术,如JavaScript、HTML、CSS、WebRTC等。同时还需要掌握相关的计算机视觉和人工智能技术。值得注意的是,由于Web端的计

PHP中的模运算符(%)是用来获取两个数值相除的余数的。在本文中,我们将详细讨论模运算符的作用及用法,并提供具体的代码示例来帮助读者更好地理解。1.模运算符的作用在数学中,当我们将一个整数除以另一个整数时,会得到一个商和一个余数。例如,当我们将10除以3时,商为3,余数为1。模运算符就是用来获取这个余数的。2.模运算符的用法在PHP中,使用%符号来表示模

C语言作为一门广泛应用在软件开发领域的编程语言,是很多程序员初学者的首选。学习C语言不仅可以帮助我们建立编程的基础知识,还可以提升我们解决问题和思考的能力。本文将详细介绍一条C语言学习的路线图,帮助初学者更好地规划自己的学习进程。1.学习基本语法在开始学习C语言之前,我们首先需要了解C语言的基本语法规则。这包括变量和数据类型、运算符、控制语句(如if语句、

Linux系统调用system()函数详解系统调用是Linux操作系统中非常重要的一部分,它提供了一种与系统内核进行交互的方式。其中,system()函数是一个常用的系统调用函数之一。本文将详细介绍system()函数的使用方法,并提供相应的代码示例。系统调用的基本概念系统调用是用户程序与操作系统内核交互的一种方式。用户程序通过调用系统调用函数来请求操作系统

Linux的curl命令详解摘要:curl是一种强大的命令行工具,用于与服务器进行数据通信。本文将介绍curl命令的基本用法,并提供实际的代码示例,帮助读者更好地理解和应用该命令。一、curl是什么?curl是一个命令行工具,用于发送和接收各种网络请求。它支持多种协议,如HTTP、FTP、TELNET等,并提供了丰富的功能,如文件上传、文件下载、数据传输、代

Promise.resolve()详解,需要具体代码示例Promise是JavaScript中一种用于处理异步操作的机制。在实际开发中,经常需要处理一些需要按顺序执行的异步任务,而Promise.resolve()方法就是用来返回一个已经Fulfilled状态的Promise对象。Promise.resolve()是Promise类的一个静态方法,它接受一个
