数据结构与算法专题——第十题 输入法跳不过的坎-伸展树

我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢。

一:伸展树

1:思想

伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这棵树中的节点越来越多的情况下就会呈现下旋,所以复杂度只会递增,伸展树的想法就是在第一次查询时树里面会经过一阵痉挛把“节点7”顶成“根节点”,操作类似AVL的双旋转,比如下图:

当我们再次查询同样的”数字7“时,直接在根节点处O(1)取出,当然这算是一个最理想的情况,有时痉挛过度,会出现糟糕的”链表“,也就退化了到O(N),所以伸展树讲究的是”摊还时间“,意思就是说在”连续的一系列操作中的平均时间“,当然可以保证是log(N)。

2:伸展方式

不知道大家可否记得,在AVL中的旋转要分4个情况,同样伸展树中的伸展需要考虑6种情况,当然不考虑镜像的话也就是3种情况,从树的伸展方向上来说有“自下而上”和“自上而下"的两种方式,考虑到代码实现简洁,我还是说下后者。

1) 自上而下的伸展

这种伸展方式会把树切成三份,L树,M树,R树,考虑的情况有:单旋转,“一字型”旋转,“之字形”旋转。

  • 单旋转

从图中我们可以看到,要将“节点2”插入到根上,需要将接近于“节点2”的数插入到根上,也就是这里的“节点7”,首先树被分成了3份,初始情况,L和R树是“空节点”,M是整棵树,现在需要我们一步一步拆分,当我们将“节点2”试插入到“节点7”的左孩子时,发现“节点7”就是父节点,满足“单旋转”情况,然后我们将整棵树放到“R树”中的left节点上,M此时是一个逻辑上的空节点,然后我们将R树追加到M树中。L树追加到M的左子树中,最后我们将“节点2”插入到根节点上。说这么多有点拗口,伸展树比较难懂,需要大家仔细品味一下。

  • 一字型

一字型旋转方式与我们AVL中的“单旋转”类似,首先同样我们切成了三份,当我们"预插入20时”,发现20的“父节点”是根的右孩子,而我们要插入的数字又在父节点的右边,此时满足”一字型“旋转,我们将7,10两个节点按照”右右情况”旋转,旋转后“节点10"的左孩子放入到L树的right节点,"节点10”作为中间树M,最后将20插入根节点。

  • 之字形

之字形有点类似AVL中的“双旋转”,不过人家采取的策略是不一样的,当我们试插入“节点9”,同样发现“父节点”是根的右儿子,并且“节点9”要插入到父节点的内侧,根据规则,需要将“父节点10”作为M树中的根节点,“节点7”作为L树中的right节点,然后M拼接L和R,最后将节点9插入到根上。

3:基本操作

1) 节点定义

我们还是采用普通二叉树中的节点定义,也就没有了AVL那么烦人的高度信息。


public class BinaryNode<T>{// Constructorspublic BinaryNode(T theElement) : this(theElement, null, null) { }public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt){element = theElement;left = lt;right = rt;}public T element;public BinaryNode<T> left;public BinaryNode<T> right;}

2) 伸展

这里为了编写代码方便,我采用的是逻辑nullNode节点,具体伸展逻辑大家可以看上面的图。


#region 伸展/// <summary>/// 伸展/// </summary>/// <param name="Key"></param>/// <param name="tree"></param>/// <returns></returns>public BinaryNode<T> Splay(T Key, BinaryNode<T> tree){BinaryNode<T> leftTreeMax, rightTreeMin;header.left = header.right = nullNode;leftTreeMax = rightTreeMin = header;nullNode.element = Key;while (true){int compareResult = Key.CompareTo(tree.element);if (compareResult < 0){//如果成立,说明是”一字型“旋转if (Key.CompareTo(tree.left.element) < 0)tree = rotateWithLeftChild(tree);if (tree.left == nullNode)break;//动态的将中间树的”当前节点“追加到 R 树中,同时备份在header中rightTreeMin.left = tree;rightTreeMin = tree;tree = tree.left;}else if (compareResult > 0){//如果成立,说明是”一字型“旋转if (Key.CompareTo(tree.right.element) > 0)tree = rotateWithRightChild(tree);if (tree.right == nullNode)break;//动态的将中间树的”当前节点“追加到 L 树中,同时备份在header中leftTreeMax.right = tree;leftTreeMax = tree;tree = tree.right;}else{break;}}/* 剥到最后一层,来最后一次切分 *///把中间树的左孩子给“左树”leftTreeMax.right = tree.left;//把中间树的右孩子给“右树”rightTreeMin.left = tree.right;/* 合并操作 *///将头节点的左树作为中间树的左孩子tree.left = header.right;//将头结点的右树作为中间树的右孩子tree.right = header.left;return tree;}#endregion

3) 插入

插入操作关键在于我们要找到接近于”要插入点“的节点,然后顶成“根节点”,也就是上面三分图中的最后一分。


#region 插入/// <summary>/// 插入/// </summary>/// <param name="Key"></param>public void Insert(T Key){if (newNode == null)newNode = new BinaryNode<T>(default(T));newNode.element = Key;if (root == nullNode){newNode.left = newNode.right = nullNode;root = newNode;}else{root = Splay(Key, root);int compareResult = Key.CompareTo(root.element);if (compareResult < 0){newNode.left = root.left;newNode.right = root;root.left = nullNode;root = newNode;}elseif (compareResult > 0){newNode.right = root.right;newNode.left = root;root.right = nullNode;root = newNode;}elsereturn;}newNode = null;}#endregion

4) 删除

删除操作也要将节点伸展到根上,然后进行删除,逻辑很简单。


#region 删除/// <summary>/// 删除/// </summary>/// <param name="Key"></param>public void Remove(T Key){BinaryNode<T> newTree;//将删除结点顶到根节点root = Splay(Key, root);//不等于说明没有找到if (root.element.CompareTo(Key) != 0)return;//如果左边为空,则直接用root的右孩子接上去if (root.left == nullNode){newTree = root.right;}else{newTree = root.left;newTree = Splay(Key, newTree);newTree.right = root.right;}root = newTree;}#endregion

伸展树可以总结成一幅图:

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

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

相关文章

「软件项目管理」一文详解软件项目成本计划

软件项目成本计划序言一、成本估算的定义二、估算的基本概念1、关于估算2、软件项目规模3、软件规模单位4、软件项目成本5、成本单位6、软件规模和软件成本的关系7、成本估算结果三、成本估算过程1、估算输入2、估算处理3、估算输出四、成本估算方法1、代码行估算法&#xff08…

在腾讯云云函数计算上部署.NET Core 3.1

云厂商&#xff08;腾讯云、Azure等&#xff09;提供了Serverless服务&#xff0c;借助于Serverless&#xff0c;开发人员可以更加专注于代码的开发&#xff0c;减少运维的成本。腾讯云的函数计算提供了很多运行库&#xff0c;对.NET的支持需要通过custom runtime 来支持&#…

MassTransit Get Started-

MassTransit&#xff1a;是一款.NET的分布式应用程序框架&#xff08;开源、免费&#xff09;。通过MassTransit&#xff0c;可以轻松创建利用基于消息的、松耦合异步通信的应用程序和服务&#xff0c;以提高可用性&#xff0c;可靠性和可伸缩性。MassTransit本身定位轻量级的服…

「软件项目管理」成本估算模型——Walston-Felix模型和COCOMO Ⅱ模型

Walston-Felix模型和COCOMO Ⅱ模型序言一、Walston-Felix模型1. 公式2. 举例二、COCOMO模型&#xff08;Constructive Cost Model&#xff09;1. 模型定义2. COCOMO模型的发展3. COCOMO基本原理三、COCOMO 811. 模型级别2. 项目类型3. 基本COCOMO-81&#xff08;1&#xff09;公…

图书管理(SSM)

一:前言 这仅仅是个小demo,体验了一把SSM配置地狱&#xff0c;就简单的实现CRUD 二:相关技术 MybatisSpringSpringMVCMysqlMavenTomcat 三:功能展示 1:查询 2:增加 3:删除 4&#xff1a;修改 四:上码 1&#xff1a;导入依赖 <?xml version"1.0" encoding&…

ASP.NET Core Cookie SameSite

在较多的项目中&#xff0c;Cookie 是比较常用的一种状态保持的选择。比如常见的例子&#xff1a;用户登录成功后&#xff0c;服务器通过 set-cookie 将会话Id设置到当前域下&#xff0c;前端在调用后端接口时&#xff0c;会自动将同域下的 Cookie 携带上&#xff0c;然后后端接…

还不会ts?一文带你打开ts的大门

一文带你打开ts的大门序言一、什么是TypeScript&#xff1f;1、编程语言的类型2、TypeScript究竟是什么&#xff1f;二、为什么要学习TypeScript&#xff1f;1、程序更容易理解2、效率更高3、更少的错误4、非常好的包容性5、一点小缺点三、typescript入门1、如何安装TypeScript…

idea中右侧的Maven框消失了

我掉 芭比Q了 idea中右侧的maven框居然没了 消失不见了 跟一束烟似的 没了 解决&#xff08;网上找的&#xff09;

「软件项目管理」一文详解软件项目进度计划

软件项目进度计划序言一、进度及任务的定义1. 进度2. 任务3. 产品和任务的关系二、任务关联关系1. 定义2. 任务(活动)之间的关系3. 任务关系矩阵4. 任务关联关系的依据三、进度管理图示1. 甘特图2. 网络图&#xff08;1&#xff09;定义&#xff08;2&#xff09;常用的网络图Ⅰ…

过年烟花特效

一&#xff1a;前言 过年了 菜鸡杰 在这给CSDN的伙计们拜年了 这一段时间以来 我特别感谢 我的粉丝 正是因为有你们的陪伴 所以我才会很大动力 去写博客&#xff0c;所以我就准备了一个特效给粉丝们 现在过年不让放烟花 我就改了个代码 改成烟花特效 送给大家 二:特效描述 这…

「软件项目管理」一文详解软件项目质量计划

一文详解软件项目质量计划&#x1f3a9;前言&#x1f452;一、质量概述1. 质量与软件质量2. 质量成本二、质量模型1. 定义2. 几种模型3. 模型解读&#xff08;1&#xff09;Bohem质量模型&#xff08;2&#xff09;McCall质量模型&#xff08;3&#xff09;ISO/IEC 9126质量模型…

WSL2 支持挂载物理磁盘,Windows 可直接访问 ext4

喜欢就关注我们吧&#xff01;最新的 Windows Insiders Preview Build 为 WSL2 的命令行程序增加了新功能&#xff1a;wsl --mount&#xff0c;这个新参数支持在 WSL2 添加和挂载物理磁盘&#xff0c;用户通过它能访问 Windows 没有原生支持的 Linux 文件系统&#xff08;例如 …

2招解决并发问题,省几百万设备费用!说穿了很简单...

经大佬介绍&#xff0c;接了个技术顾问的私活儿&#xff0c;3天搞定报酬8000&#xff0c;Mark一下&#xff0c;也分享下经验心得。&#xff08;经大家要求&#xff0c;文末增加了一段接私活儿经验&#xff09;背景交代甲方是广东某国企信息部&#xff0c;美其名曰是邀请技术顾问…

「软件项目管理」一文详解软件配置管理计划

一文详解软件配置管理计划前言一、配置管理概述1. 配置管理(SCM)定义2. 软件配置项目(SCI)3. 基线4. 软件配置控制委员会&#xff08;SCCB&#xff09;二、软件配置管理过程1. 管理过程2. 管理过程解析&#xff08;1&#xff09;配置项标识、跟踪I. 定义II. SPM项目要求&#x…

Kubernetes探针踩坑记

1. 荒腔走板最近一两个月生产K8s集群频繁出现短时503 Service Temporarily Unavailable&#xff0c;还不能主动复现&#xff0c;相当郁闷&#xff0c;压力山大。HTTP 5xx响应状态码用于定义服务端错误。500 Internal Server Error&#xff1a;所请求的服务器遇到意外的情况并阻…

「软件项目管理」一文了解软件项目团队计划

一文了解软件项目团队计划序言一、人力资源计划1. 团队定义2. 项目组织结构&#xff08;1&#xff09;职能型&#xff08;2&#xff09;项目型&#xff08;3&#xff09;矩阵型3. 责任分配矩阵二、项目干系人计划1. 干系人定义2. 干系人识别3. 干系人参与分类4. 干系人参与评估…

leetcode40. 组合总和 II

一:题目 二:上码 class Solution { public:/**思路:1.题目中说的每个数字只在每个组合中使用一次的话 我们可以考虑 在递归遍历的时候 index1不断缩小范围&#xff08;因为这也是在一个大的集合中挑选小的集合&#xff0c;所以是需要记录index的&#xff09;2.但是我们在填写…

DDD:架构思想的旧瓶新酒?阿里等大厂却对它趋之若鹜!

4月&#xff0c;InfoQ 发布了软件架构与设计的趋势报告。在报告中可以看出&#xff0c;微服务、领域驱动设计等已经非常流行&#xff0c;并成为目前软件开发行业的主流趋势。大家都知道&#xff0c;微服务划分的一个重要理论基础就是领域驱动设计。但由于 DDD 门槛高、概念多&a…

「软件项目管理」一文浅谈软件项目风险计划

一文浅谈软件项目风险计划序言一、风险基本概念1. 风险的定义及特性2. 风险三要素3. 风险图示3. 风险类型&#xff08;1&#xff09;预测角度&#xff08;2&#xff09;范围角度二、风险管理过程三、风险识别1. 定义2. 风险识别方法3. 风险条目检查表之七个条目四、风险评估1. …

.NET Core 下使用 gRPC

gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。https://grpc.io/docs/guides/https://github.com/grpc/grpc-dotnethttps://docs.microsoft.com/zh-cn/aspnet/core/grpc“gRPC 的主要优点现代高性能轻量级 RPC 框架。协定优先 API 开发&#xff0c;默认使用协议缓冲区…