数据结构——配对堆

引入

配对堆是一个支持插入查询/删除最小值合并修改元素等操作的数据结构,是一种可并堆。有速度快和结构简单的优势,但由于其为基于势能分析的均摊复杂度,无法可持久化。

定义

配对堆是一棵满足堆性质的带权多叉树(如下图),即每个节点的权值都小于或等于他的所有儿子(以小根堆为例,下同)。
在这里插入图片描述

通常我们使用儿子 - 兄弟表示法储存一个配对堆(如下图),一个节点的所有儿子节点形成一个单向链表。每个节点储存第一个儿子的指针,即链表的头节点;和他的右兄弟的指针。

这种方式便于实现配对堆,也将方便复杂度分析。

在这里插入图片描述

struct Node {T v;  // T为权值类型Node *child, *sibling;// child 指向该节点第一个儿子,sibling 指向该节点的下一个兄弟。// 若该节点没有儿子/下个兄弟则指针指向 nullptr。
};

从定义可以发现,和其他常见的堆结构相比,配对堆不维护任何额外的树大小,深度,排名等信息(二叉堆也不维护额外信息,但它是通过维持一个严格的完全二叉树结构来保证操作的复杂度),且任何一个满足堆性质的树都是一个合法的配对堆,这样简单又高度灵活的数据结构奠定了配对堆在实践中优秀效率的基础;作为对比,斐波那契堆糟糕的常数就是因为它需要维护很多额外的信息。

配对堆通过一套精心设计的操作顺序来保证它的总复杂度,原论文1将其称为「一种自调整的堆(Self Adjusting Heap)」。在这方面和 Splay 树(在原论文中被称作「Self Adjusting Binary Tree」)颇有相似之处。

过程

查询最小值

从配对堆的定义可看出,配对堆的根节点的权值一定最小,直接返回根节点即可。

合并

合并两个配对堆的操作很简单,首先令两个根节点较小的一个为新的根节点,然后将较大的根节点作为它的儿子插入进去。(见下图)

在这里插入图片描述
需要注意的是,一个节点的儿子链表是按插入时间排序的,即最右边的节点最早成为父节点的儿子,最左边的节点最近成为父节点的儿子。

实现

Node* meld(Node* x, Node* y) {// 若有一个为空则直接返回另一个if (x == nullptr) return y;if (y == nullptr) return x;if (x->v > y->v) std::swap(x, y);  // swap后x为权值小的堆,y为权值大的堆// 将y设为x的儿子y->sibling = x->child;x->child = y;return x;  // 新的根节点为 x
}

插入

合并都有了,插入就直接把新元素视为一个新的配对堆和原堆合并就行了。

删除最小值

首先要提及的一点是,上文的几个操作都十分偷懒,完全没有对数据结构进行维护,所以我们需要小心设计删除最小值的操作,来保证总复杂度不出问题。

根节点即为最小值,所以要删除的是根节点。考虑拿掉根节点之后会发生什么:根节点原来的所有儿子构成了一片森林;而配对堆应当是一棵树,所以我们需要通过某种顺序把这些儿子全部合并起来。

一个很自然的想法是使用 m e l d meld meld 函数把儿子们从左到右挨个并在一起,这样做的话正确性是显然的,但是会导致单次操作复杂度退化到 O ( n ) O(n) O(n)

为了保证总的均摊复杂度,需要使用一个「两步走」的合并方法:

1.把儿子们两两配成一对,用 meld 操作把被配成同一对的两个儿子合并到一起(见下图 1),
2.将新产生的堆** 从右往左**(即老的儿子到新的儿子的方向)挨个合并在一起(见下图 2)。

在这里插入图片描述
在这里插入图片描述

先实现一个辅助函数merges,作用是合并一个节点的所有兄弟。

实现

Node* merges(Node* x) {if (x == nullptr || x->sibling == nullptr)return x;  // 如果该树为空或他没有下一个兄弟,就不需要合并了,return。Node* y = x->sibling;                // y 为 x 的下一个兄弟Node* c = y->sibling;                // c 是再下一个兄弟x->sibling = y->sibling = nullptr;   // 拆散return meld(merges(c), meld(x, y));  // 核心部分
}

最后一句话是该函数的核心,这句话分三部分:
1.meld(x,y)「配对」了 x 和 y。
2.merges( c ) 递归合并 c 和他的兄弟们。
3.将上面 2 个操作产生的 2 个新树合并。

需要注意到的是,上文提到了第二步时的合并方向是有要求的(从右往左合并),该递归函数的实现已保证了这个顺序,如果读者需要自行实现迭代版本的话请务必注意保证该顺序,否则复杂度将失去保证。

有了 merges 函数,delete-min 操作就显然了。

Node* delete_min(Node* x) {Node* t = merges(x->child);delete x;  // 如果需要内存回收return t;
}

减小一个元素的值

要实现这个操作,需要给节点添加一个「父」指针,当节点有左兄弟时,其指向左兄弟而非实际的父节点;否则,指向其父节点。

首先节点的定义修改为:

struct Node {LL v;int id;Node *child, *sibling;Node *father;  // 新增:父指针,若该节点为根节点则指向空节点 nullptr
};

meld 操作修改为:

Node* meld(Node* x, Node* y) {if (x == nullptr) return y;if (y == nullptr) return x;if (x->v > y->v) std::swap(x, y);if (x->child != nullptr) {  // 新增:维护父指针x->child->father = y;}y->sibling = x->child;y->father = x;  // 新增:维护父指针x->child = y;return x;
}

merges操作修改为:

Node *merges(Node *x) {if (x == nullptr) return nullptr;x->father = nullptr;  // 新增:维护父指针if (x->sibling == nullptr) return x;Node *y = x->sibling, *c = y->sibling;y->father = nullptr;  // 新增:维护父指针x->sibling = y->sibling = nullptr;return meld(merges(c), meld(x, y));
}

现在我们来考虑如何实现 decrease-key 操作。
首先我们发现,当我们减少节点 x 的权值之后,以 x 为根的子树仍然满足配对堆性质,但 x 的父亲和 x 之间可能不再满足堆性质。
因此我们把整棵以 x 为根的子树剖出来,现在两棵树都符合配对堆性质了,然后把他们合并起来,就完成了全部操作。

// root为堆的根,x为要操作的节点,v为新的权值,调用时需保证 v <= x->v
// 返回值为新的根节点
Node *decrease_key(Node *root, Node *x, LL v) {x->v = v;                 // 更新权值if (x == root) return x;  // 如果 x 为根,则直接返回// 把x从fa的子节点中剖出去,这里要分x的位置讨论一下。if (x->father->child == x) {x->father->child = x->sibling;} else {x->father->sibling = x->sibling;}if (x->sibling != nullptr) {x->sibling->father = x->father;}x->sibling = nullptr;x->father = nullptr;return meld(root, x);  // 重新合并 x 和根节点
}

复杂度分析

配对堆结构与实现简单,但时间复杂度分析并不容易。

原论文1仅将复杂度分析到 melddelete-min 操作均为均摊 O ( log ⁡ n ) O(\log n) O(logn),但提出猜想认为其各操作都有和斐波那契堆相同的复杂度。

遗憾的是,后续发现,不维护额外信息的配对堆,在特定的操作序列下,decrease-key 操作的均摊复杂度下界至少为 Ω ( log ⁡ log ⁡ n ) 2 \Omega (\log \log n)2 Ω(loglogn)2

目前对复杂度上界比较好的估计有,Iacono 的 O ( 1 ) O(1) O(1) meld,$O(\log n) $decrease;Pettie 的 O ( 2 2 log ⁡ log ⁡ n ) O(2^{2 \sqrt{\log \log n}}) O(22loglogn ) meld 和 decrease。需要注意的是,前述复杂度均为均摊复杂度,因此不能对各结果分别取最小值。

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

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

相关文章

rabbitmq的持久化

目录 队列实现持久化 如何删除队列​编辑 消息实现持久化 不公平分发 如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失。默认情况下 RabbitMQ 退出或由于某种原因崩溃时&#xff0c;它忽视队列和消息&#xff0c;除非告知它不要这样做。确保消息不会丢失需…

第4章 微服务框架主体搭建

mini商城第4章 微服务框架主体搭建 一、课题 框架搭建 二、回顾 1、整体业务功能分析 2、根据业务需求设计表结构及字段 三、目标 1、版本控制器的搭建使用 2、能独立自主的搭建微服务框架 3、学会考虑一些公共的工具组件 4、网关模块的应用 四、内容 第1章 版本控…

【量化课程】08_1.机器学习量化策略基础实战

文章目录 1. 常用机器学习模型1.1 回归模型1.2 分类模型1.2.1 SVC介绍1.2.2 SVC在量化策略中的应用 2. 机器学习量化策略实现的基本步骤3. 策略实现 1. 常用机器学习模型 1.1 回归模型 线性回归多层感知器回归自适应提升树回归随机森林回归 1.2 分类模型 线性分类支持向量机…

HDFS中snapshot快照机制

HDFS中snapshot快照机制 介绍作用功能实现相关命令和操作相关命令 介绍 snapshot是数据存储的某一时刻的状态记录&#xff0c;备份&#xff08;backup&#xff09;则是数据存储的某一个时刻的副本HDFS snapshot快照是整个文件系统或某个目录在某个时刻的镜像&#xff0c;该镜像…

【碎碎念随笔】1、回顾我的电脑和编程经历

✏️ 闲着无事&#xff0c;讲述一下我的计算机和代码故事 一、初识计算机 &#x1f5a5;️ 余家贫&#xff0c;耕植无钱买电脑。大约六年级暑假&#xff0c;我在姐姐哪儿第一次接触到了计算机&#xff08;姐姐也是买的二手&#xff09;。 &#x1f5a5;️ 计算机真有趣&#x…

【JavaEE进阶】Bean 作用域和生命周期

文章目录 一. 关于Bean作用域的实例1. lombok2. 实例代码 二. 作用域定义1. Bean的六种作用域2. 设置作用域 三. Spring 执行流程和 Bean 的生命周期1. Spring 执行流程2. Bean生命周期 一. 关于Bean作用域的实例 注意在此例子中需要用到lombok 1. lombok lombok是什么? Lo…

java知识-JVM线程四大引用

一、JVM (1) 基本概念&#xff1a; JVM 是可运行 Java 代码的假想计算机 &#xff0c;包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收&#xff0c;堆 和 一个存储方法域。JVM 是运行在操作系统之上的&#xff0c;它与硬件没有直接 的交互。 (2) 运行过程&#x…

【Tomcat】tomcat的多实例和动静分离

多实例&#xff1a; 在一台服务器上有多台Tomcat&#xff1b;就算是多实例 安装telnet服务&#xff0c;可以用来测试端口通信是否正常 yum -y install telnettelnet 192.168.220.112 80 tomcat的日志文件 cd /usr/local/tomcat/logsvim catalina.out Tomcat多实例部署&…

浅谈限流式保护器在住宅电气防火的应用

安科瑞 华楠 【摘要】随着人民生活水平的提高&#xff0c;家用大功率电器普遍被使用&#xff0c;导致用电量剧增&#xff0c;电气火灾频发。文章分析了电气火灾发生的原因&#xff0c;并时电气火灾的防范措施进行了探讨。 【关键词】电气火灾&#xff1b;原因&#xff1b;防范…

springboot整合kafka多数据源

整合kafka多数据源 项目背景依赖配置生产者消费者消息体 项目背景 在很多与第三方公司对接的时候&#xff0c;或者处在不同的网络环境下&#xff0c;比如在互联网和政务外网的分布部署服务的时候&#xff0c;我们需要对接多台kafka来达到我们的业务需求&#xff0c;那么当kafk…

5.1 web浏览安全

数据参考&#xff1a;CISP官方 目录 Web应用基础浏览器所面临的安全威胁养成良好的Web浏览安全意识如何安全使用浏览器 一、Web应用基础 1、Web应用的基本概念 Web ( World wide Web) 也称为万维网 脱离单机Web应用在互联网上占据了及其重要的地位Web应用的发展&#xf…

macOS(m芯片)连接服务器及其进行文件传输的各种方式的详解

说明&#xff1a;使用了macOS后发现&#xff0c;win系统能使用的xshell、xftp等连接服务器及其文件传输等软件均不能使用了&#xff0c;没有兼容的版本。所以我们刚切换到mac系统该如何去适应呢。 一、连接远程服务器 macOS中前文也说道我们使用的是iterm2进行终端控制的&…