Python3实现红黑树[下篇]

Python3实现红黑树[下篇]

我写的红黑树的上篇在这里:https://blog.csdn.net/qq_18138105/article/details/105190887

这是我近期看的文章 https://www.cnblogs.com/gcheeze/p/11186806.html

我看了很多关于红黑树删除的文章和博客,介绍得是相当相当的复杂,那么大猪猪就带大家好好捋一捋。

我们用一个简单的方式去处理,我们找到要删除一个元素,我们用指针d指向它,然后进行判断:

1. 若d指向的是叶子节点,则结束循环
2. 若d指向的节点有右子树,则找到它的直接后继,交换它和直接后继的数据,d指向它的直接后,重复步骤1
3. 若d指向的节点有左子树,则找到它的直接前驱,交换它和直接前驱的数据,d指向它的直接前驱,重复步骤1

这里或许同学们会有疑问,什么是直接后继,什么是直接前驱。
借用一下度娘的图吧,如图,F的直接后继是H,F的直接前驱是D。再补充远侄近侄的概念,如图C的远侄是G,C的近侄是H,这个概念接下来会用到!
在这里插入图片描述
回到正题,d最终指向的节点,就是我们要删除的节点。红黑树删除的问题就大大简化了,这样处理的就是叶子节点的删除了!

假设d指向的节点是D,定义D的父节点是P,D的兄弟节点是S。开始进行判断:

  • D是红色,直接删除(因为:删除它不会影响红黑树的黑色高度属性)
  • D是黑色,分2种大情况
    • S是红色
      • 若D是P的左孩子,则S左旋,重新开始判断
      • 若D是P的右孩子,则S右旋,重新开始判断
    • S是黑色,分3种小情况
      • D的远侄为红
        • 若D是P的左孩子,S左旋,远侄变黑,删除D
        • 若D是P的右孩子,S右旋,远侄变黑,删除D
      • D的远侄为黑,近侄为红
        • 若D是P的左孩子,近侄记为SL,SL右旋,SL左旋,S变黑,删除D
        • 若D是P的右孩子,近侄记为SR,SR左旋,SR右旋,S变黑,删除D
      • D的远侄和近侄都是黑
        • 若P是红色,则S变红,P变黑,删除D
        • 若P是黑色,则S变红,删除D。但是!经过P的路径上的黑色节点数会少1,因此要继续向上进行平衡操作,然后d指向P,重新开始判断(只不过后续步骤不再需要删除节点了),一直向上判断,直到指向根节点为止。

代码实现:

# 红黑树节点
class RBN(object):def __init__(self, data):self.data = data  # 数据域self.color = 0  # 0红 1黑self.left = Noneself.right = Noneself.parent = None# 红黑树
class RBT(object):def __init__(self):self.root = Nonedef treePrint(self):print('红黑树: start')self.midTraverse(self.root)print('红黑树: end')# 中序遍历def midTraverse(self, x):if x == None:returnself.midTraverse(x.left)colorStr = '黑' if x.color == 1 else '红'parentStr = '父=' + ('nil' if x.parent == None else str(x.parent.data))print(x.data, colorStr, parentStr)self.midTraverse(x.right)# 添加一个节点def add(self, x):# 如果没有根节点 作为根节点if self.root == None:self.root = xx.color = 1  # 根节点为黑色return# 寻找合适的插入位置p = self.rootwhile p != None:if x.data < p.data:if p.left == None:p.left = xx.parent = pself.addFix(x)breakp = p.leftelif x.data > p.data:if p.right == None:p.right = xx.parent = pself.addFix(x)breakp = p.rightelse:return# 调整红黑树def addFix(self, x):while True:if x == self.root:  # 如果处理到根节点了 则着色为黑x.color = 1returnp = x.parent  # 爸爸if p.color == 1 or x.color == 1:  # 自己和爸爸只要有一个是黑的 就构不成双红 则返回return# 接下来分析红爸爸情况g = p.parent  # 爷爷 红爸爸肯定有爸爸,因为红色绝不是根节点u = g.left if p == g.right else g.right  # 叔叔 叔叔可能为空节点if u != None and u.color == 0:  # 红叔叔 则着色 然后从爷爷开始向上继续调整u.color = p.color = 1  # 叔叔和爸爸都变黑色g.color = 0  # 爷爷变红色x = g  # x指向爷爷,然后继续循环continue# 接下来分析黑叔叔得情况 有四种情况 左左,左右,右左,右右if p == g.left and x == p.left:  # 左左# 以爸爸为支点右旋爷爷self.rotateRight(p)elif p == g.left and x == p.right:  # 左右# 以x为支点左旋爸爸self.rotateLeft(x)# 以x为支点右旋爷爷(上面的旋转把爷爷变成了新爸爸)self.rotateRight(x)elif p == g.right and x == p.right:  # 右右 其实就是 左左的镜像# 以爸爸为支点左旋爷爷self.rotateLeft(p)elif p == g.right and x == p.left:  # 右左 其实就是 左右的镜像# 以x为支点右旋爸爸self.rotateRight(x)# 以x为支点左旋爷爷(上面的旋转把爷爷变成了新爸爸)self.rotateLeft(x)## 关于红黑树的旋转,一直是个难搞的点# 这里我提供一个口诀:#   右旋: 支点占旋点原位,支点的右给旋点作为左,旋点作为支点的右#   左旋: 支点占旋点原位,支点的左给旋点作为右,旋点作为支点的左## 右旋 p支点def rotateRight(self, p):g = p.parent  # 支点的父节点就是旋点# 右旋gif g == self.root:  # 若g是根节点 则p升为根节点self.root = pp.parent = Noneelse:  # 若g不是根节点 那么必然存在g.parent p占据g的位置gp = g.parentp.parent = gpif g == gp.left:gp.left = pelse:gp.right = pg.left = p.rightif p.right != None:p.right.parent = gp.right = gg.parent = p# g和p颜色交换p.color, g.color = g.color, p.color# 左旋 p 支点def rotateLeft(self, p):g = p.parent  # 支点的父节点就是旋点# 左旋gif g == self.root:  # 若g是根节点 则p升为根节点self.root = pp.parent = Noneelse:  # 若g不是根节点 那么必然存在g.parent p占据g的位置gp = g.parentp.parent = gpif g == gp.left:gp.left = pelse:gp.right = pg.right = p.leftif p.left != None:p.left.parent = gp.left = gg.parent = p# g和p颜色交换p.color, g.color = g.color, p.color# 删除一个节点def delete(self, x):# 查找要删除的节点d = self.rootwhile d != None:if x.data < d.data:d = d.leftelif x.data > d.data:d = d.rightelse:breakif d == None:print('要删除的', x.data, '已经不存在了')return# 如果要删除的节点不是叶子节点,我们需要做d的指向转移,直到d指向的是叶子节点就结束循环while d.left != None or d.right != None:# 如果存在右子树 则找直接后继(也就是右子树的最左后代),交换数据, d指向这个后继,重复循环if d.right != None:nextNode = self.getMostLeft(d.right)d.data, nextNode.data = nextNode.data, d.datad = nextNodecontinue# 如果存在左子树 则找到直接前驱(也就是左子树的最右后代),交换数据,d指向这个前驱,重复循环elif d.left != None:preNode = self.getMostRight(d.left)d.data, preNode.data = preNode.data, d.datad = preNodecontinue# print('要删除的是', d.data);# 接下来处理要删除的节点是叶子节点的情况吧 #needDelete = Truewhile True:# 如果d是根节点,直接删除if self.root == d:if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 如果d的是红色,那么直接删除if self.isRed(d):if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 如果d是黑色,设s指向兄弟节点(根据红黑树左右子树黑色高度相等的性质,s指向的节点必定存在),分好几种情况p = d.parents = p.right if d == p.left else p.left# print(d.data, d.color, p.data)# 1 如果s是红色 if self.isRed(s):if d == p.left:self.rotateLeft(s)else:self.rotateRight(s)continue # 旋转后 再重新判断sl = s.leftsr = s.right# 2 如果s是黑色,又分好几种情况if d == p.left:  # d是p的左孩子# 2.1 如果d的远侄为红(有远侄的话必定为红,否则不满足红黑树的黑色高度性质)if self.isRed(sr):self.rotateLeft(s)sr.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.2 如果d的远侄为黑,近侄为红(有近侄的话必定为红,否则不满足红黑树的黑色高度性质)if not self.isRed(sr) and self.isRed(sl):self.rotateRight(sl)self.rotateLeft(sl)s.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3 如果d的远侄和近侄都是黑,也就是d是叶子节点,分2种情况# 2.3.1 如果p是红色 s变红 p变黑 删除dif p.color == 0:s.color = 0p.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3.1 如果p是黑色 s变红 删除d 然后d指向p重复判断步骤s.color = 0if needDelete:self.deleteDirectly(d)needDelete = Falsed = pcontinueelse:  # d是p的右孩子# 2.1 如果d的远侄为红(有远侄的话必定为红,否则不满足红黑树的黑色高度性质)if self.isRed(sl):self.rotateRight(s)sl.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.2 如果d的远侄为黑,近侄为红(有近侄的话必定为红,否则不满足红黑树的黑色高度性质)if not self.isRed(sl) and self.isRed(sr):self.rotateLeft(sr)self.rotateRight(sr)s.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3 如果d的远侄和近侄都是黑,也就是d是叶子节点,分2种情况# 2.3.1 如果p是红色 s变红 p变黑 删除dif p.color == 0:s.color = 0p.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3.1 如果p是黑色 s变红 删除d 然后d指向p重复判断步骤s.color = 0if needDelete:self.deleteDirectly(d)needDelete = Falsed = pcontinue# 是否是红色节点def isRed(self, x):return x != None and x.color == 0# 找到x的最左的后代def getMostLeft(self, x):while x.left != None:x = x.leftreturn x# 找到y的最右后代def getMostRight(self, x):while x.right != None:x = x.rightreturn x# 直接删除x节点def deleteDirectly(self, x):if x == self.root:self.root = Noneelse:p = x.parentif x == p.left:p.left = Noneelse:p.right = Noneif __name__ == '__main__':rbt = RBT()# datas = [10, 20, 30, 15]# datas = [11, 2, 14, 1, 7, 15, 5, 8, 4]datas = [12, 1,   9,   2,   0,  11,  7,  19,   4,  15,18, 5,   14,  13, 10,   16,   6,   3,   8,   17]for x in datas:rbt.add(RBN(x))print('=====================================================')rbt.treePrint()print('=====================================================')rbt.delete(RBN(12))rbt.delete(RBN(1))rbt.delete(RBN(9))rbt.delete(RBN(2))rbt.delete(RBN(0))rbt.delete(RBN(11))rbt.delete(RBN(7))rbt.delete(RBN(19))rbt.delete(RBN(4))rbt.delete(RBN(15))rbt.delete(RBN(18))# rbt.delete(RBN(5))# rbt.delete(RBN(14))# rbt.delete(RBN(13))# rbt.delete(RBN(10))# rbt.delete(RBN(16))# rbt.delete(RBN(6))# rbt.delete(RBN(3))# rbt.delete(RBN(8))# rbt.delete(RBN(17))rbt.treePrint()

结论正确。
另外,再强调这两篇文章所提到的“左旋”和“右旋”:

右旋: 支点占旋点原位,支点的右给旋点作为左,旋点作为支点的右,交换支点和旋点的颜色
左旋: 支点占旋点原位,支点的左给旋点作为右,旋点作为支点的左,交换支点和旋点的颜色

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337428.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言内存泄露很严重,如何应对?

点击蓝字关注我们1. 前言最近部门不同产品接连出现内存泄漏导致的网上问题&#xff0c;具体表现为单板在现网运行数月以后&#xff0c;因为内存耗尽而导致单板复位现象。**一方面&#xff0c;内存泄漏问题属于低级错误&#xff0c;此类问题遗漏到现网&#xff0c;影响很坏&…

python发送邮件outlook_通过Python发送Outlook电子邮件?

I am using Outlook 2003. What is the best way to send email (through Outlook 2003) using Python? 解决方案 For a solution that uses outlook see TheoretiCALs answer below. Otherwise, use the smtplib that comes with python. Note that this will require your e…

C++队列queue用法详解(超详细)

点击蓝字关注我们一、定义queue是一种容器转换器模板&#xff0c;调用#include< queue>即可使用队列类。二、queue初始化queue<Type, Container> (<数据类型&#xff0c;容器类型>&#xff09;初始化时必须要有数据类型&#xff0c;容器可省略&#xff0c;省…

python随机抽取人名_python实现艾宾浩斯背单词功能,实现自动提取单词、邮件发送,再也不用担心背单词啦...

&#xfeff;已经完成了利用python爬虫实现定时QQ邮箱推送英文文章&#xff0c;辅助学习英语的项目&#xff0c;索性就一口气利用python多做一些自动化辅助英语学习的项目&#xff0c;对自己的编程能力和英文水评也有一定的帮助&#xff0c;于是在两天的努力下&#xff0c;我完…

用不到125行C语言代码就可以编写一个简单的16位虚拟机?

点击蓝字关注我们一位国外的软件工程师分享了这么一篇博文&#xff1a;Writing a simple 16 bit VM in less than 125 lines of C&#xff08;用不到 125 行 C 语言编写一个简单的 16 位虚拟机&#xff09;。博文地址&#xff1a;https://www.andreinc.net/2021/12/01/writing-…

用一个程序生成另一个程序_还有另一个报告生成器?

用一个程序生成另一个程序如果您具有业务应用程序开发的经验&#xff0c;那么很可能会遇到要求该应用程序具有灵活的报告机制的需求。 我工作的公司主要专注于开发业务解决方案&#xff0c;而报告是必不可少的&#xff0c;实际上&#xff0c;它必须包含我们开发的所有企业系统的…

CocosCreator1.x实现水流动的效果

CocosCreator1.x实现水流动的效果Cocos Creator版本&#xff1a;1.10.2 运行结果&#xff1a;(H5和原生都支持) 场景: 脚本&#xff1a; HelloWorld.js&#xff1a; let shader require(shader);cc.Class({extends: cc.Component,properties: {water: cc.Node,waterNorm…

python爬虫xpath教程_使用 Xpath 进行爬虫开发

使用 Xpath 进行爬虫开发 Xpath( XML Path Language, XML路径语言)&#xff0c;是一种在 XML 数据中查找信息的语言&#xff0c;现在&#xff0c;我们也可以使用它在 HTML 中查找需要的信息。 既然谈到 Xpath 是一门语言&#xff0c;当然它就会有自己的一些特定的语法。我们这里…

用C语言写烟花,给心中的那个人看!

点击蓝字关注我们前言程序员不懂浪漫? 大错特错&#xff01;今天就让你们看看什么是程序员的浪漫&#xff01;你向窗外看烟花&#xff0c;我在窗边看你&#xff0c;这时&#xff0c;你比烟花好看的多&#xff0c;你的眼眸倒映满天的烟火&#xff0c;我的瞳孔倒映你温柔的脸庞…

手把手教你做一个线程池--C语言版

点击蓝字关注我们1、线程池原理我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&#xff0c;这样频繁创建线程就会大大降低…

oracle 48小时内_缺血性脑梗死后48小时内使用阿替普酶能够降低脑损伤程度

一项刊登在影响因子7.6杂志Neurology上题为“Effect of IV alteplase on the ischemic brain lesion at 24–48 hours after ischemic stroke”的研究报告中&#xff0c;来自爱丁堡大学的科学家们发现&#xff0c;alteplase与病变可视性的短期进展降低相关。在荟萃分析中&#…

MySQL夺命16问,你能坚持到第几问?

点击蓝字关注我们1、数据库三大范式是什么&#xff1f;第一范式&#xff1a;每个列都不可以再拆分。第二范式&#xff1a;在第一范式的基础上&#xff0c;非主键列完全依赖于主键&#xff0c;而不能是依赖于主键的一部分。第三范式&#xff1a;在第二范式的基础上&#xff0c;非…

美图手机投射功能在哪_在Java 8中进行投射(还有其他功能?)

美图手机投射功能在哪将实例转换为设计不良的类型。 尽管如此&#xff0c;在某些情况下没有其他选择。 从第一天开始&#xff0c;执行此功能就已成为Java的一部分。 我认为Java 8提出了对这种古老技术稍加改进的需求。 静态铸造 Java中最常见的转换方法如下&#xff1a; 静态…

js箭头函数和普通函数区别

js箭头函数和普通函数区别实验环境&#xff1a;nodejs v12.16.1 箭头函数不能作为构造函数&#xff0c;而普通函数可以 箭头函数没有原型&#xff0c;而普通函数有 箭头函数return可以省略语句块。(如果>右边不是语句块&#xff0c;则代表return右边的表达式或对象) 箭…

git 更新_[技术分享T.191212]GitLab使用方法及git命令常见问题(不断更新)

该文章用于记录一些GitLab的使用指南&#xff0c;以及在实际版本控制过程中遇到的问题及解决方法&#xff0c;会尽量及时的更新~GitLab简介&#xff1a;GitLab和GitHub很相似都属于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来…

记一次开发实战-对提供接口的C/C++进行二次开发

点击蓝字关注我们一、需求描述我有一个USB5538的库和头文件&#xff0c;并通过头文件提供了接口&#xff0c;我想把它更改一下&#xff0c;编译成python可调用的模块。二、创建工程及其目录1、创建空项目2、创建目录三、创建文件1、复制文件并添加2、添加新文件并写入四、环境配…

C++是如何实现多态的

C是如何实现多态的结论&#xff1a;C通过虚函数来实现多态的&#xff0c;根本原因是派生类和基类的虚函数表的不同。 构成多态的必要条件有如下3点&#xff1a; 存在继承关系基类存在虚函数&#xff0c;且派生类有相同原型的函数遮蔽它存在基类类型的指针指向派生类对象&…

C语言实现通讯录附详细代码(动态+静态)

点击蓝字关注我们一、通讯录简介实现一个通讯录&#xff1b;通讯录可以用来存储1000个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址提供方法&#xff1a;添加联系人信息删除指定联系人信息查找指定联系人信息修改指定联系人信息显示所有联系…

Lua协程Coroutine是什么

Lua协程Coroutine是什么协程和线程不同&#xff1a; 同一时刻&#xff0c;一个多线程程序可以用多个线程同时执行&#xff1b;而协程只能有一个在执行多线程是抢占式的&#xff1b;而协程是非抢占式的&#xff0c;只有协程显示被挂起&#xff0c;才会被挂起 协程和线程的相同…

C++程序的内存分区模型-栈区堆区

点击蓝字关注我们1、栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数值&#xff0c;局部变量等&#xff08;由编译器管理其“生死”&#xff09;注意事项&#xff1a;不要返回局部变量的地址&#xff0c;栈区开辟的数据由编译器自动释放栈区代码演示&#xff…