数据结构——配对堆

[算法]优先队列之配对堆 配对堆的核心要义在于合并,只要掌握到合并的精髓就能很快的手撸一个配对堆。由于配对堆的插入复杂度只有常数级别,在通常情况下弹出最小值的性能也会由于二叉堆(Java、Python等语言中优先队列的默认实现),所以在做Prim时会有优势。 阅读详情

引入

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

定义

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

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

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

在这里插入图片描述

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
}

插入

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

删除最小值

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

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

一个很自然的想法是使用 meldmeldmeld 函数把儿子们从左到右挨个并在一起,这样做的话正确性是显然的,但是会导致单次操作复杂度退化到 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仅将复杂度分析到 meld 和 delete-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(22log⁡log⁡n)O(2^{2 \sqrt{\log \log n}})O(22loglogn​) meld 和 decrease。需要注意的是,前述复杂度均为均摊复杂度,因此不能对各结果分别取最小值。

配对堆Pairing Heap 前言 最近做一道Dijkstra的题目,因为边数实在太大了(10910910^9),用STL的priority_queue直接超时(事实上还会MLE)。有同学写配对堆的。刚好很久没学习新的数据结构了,就趁着这个机会补一补。 配对堆 配对堆 Pairing Heap 是一种实现简单、均摊复杂度优越的堆数据结构,由Michael Fredman、罗伯特·塞奇威克、Daniel Sleator... 阅读详情

相关推荐

acm-【堆】学习笔记(二叉堆、左偏树、配对堆、斐波拉契堆、斜堆、STL)

引言 本文主要针对作者本人复习使用,相关基础知识点不会讲述。 目录引言二叉堆左偏树斜堆配对堆二项堆斐波拉契堆STL中的堆堆的应用 二叉堆 二叉堆是最普通的堆,能够实现push,poppush,poppush,pop等基本操作,下面以小根堆为例给出一种常见的实现。 首先设置一个足够大的数组,我们将树的节点按照层次遍历的顺序从小到大依次放置在数组中,数组中的位置也对应是该节点的编号,如下图所示: 上图是一个典型的二叉树,每个节点上标注的数字即是该节点的编号,容易发现对于一个节点uuu而言,它的儿子分别是2u,

qq_30361651的博客 1476

配对堆(Pairing Heap)

配对堆(Pairing Heap)是一个简单实用的min-heap结构(当然也可以做成max-heap)。它是一颗多路树(multiway tree),类似于Leftist Heap和Skew Heap,但是与Binomial Tree和Fibonacci Heap不一样。它的基

ljsspace的专栏 2万+

告别反复格式化!用Ventoy制作2025年终极启动盘,Windows/Linux/macOS一网打尽

本文详细介绍了如何使用Ventoy制作2025年终极多系统启动盘,彻底告别反复格式化U盘的烦恼。Ventoy通过创新的引导加载器架构,允许用户在一个U盘内同时存放并直接启动Windows、Linux、macOS等多种系统镜像和WePE等维护工具,实现真正的非破坏性管理和极高的U盘利用率。文章涵盖从安装配置、镜像库构建到安全启动设置等全流程实战指南。

weixin_29280219的博客 242

配对堆

配对堆采用左儿子和右兄弟的表示方法。存在额外的链,如果节点是最左儿子,则链指向父节点,如果不是,则指向左兄弟。把这个数据成员叫做prev。 配对堆的基本操作有merge、insert、deleteMin、decreaseKey。合并时让较大根的子堆成为另一个最左的儿子。如下图所示: insert操作返回Position对象,传给decreaseKey操作使用。insert操作是mer

blubluhao的博客 1446

【仿真】Carla Python API实战:从零搭建自动驾驶测试环境

本文详细介绍了如何使用Carla Python API从零搭建自动驾驶测试环境。通过Carla仿真平台,开发者可以构建高度逼真的城市环境和车辆物理模型,利用Python API实现传感器配置、数据采集、场景构建及自动驾驶算法集成。文章还分享了实际项目中的调试技巧和性能优化经验,帮助读者快速掌握自动驾驶仿真测试的核心技术。

weixin_29050513的博客 213

PyTorch实战:用FGSM攻击MNIST分类器(附可视化对比)

本文通过PyTorch实战演示了如何使用FGSM对抗攻击方法攻击MNIST手写数字分类器。文章详细解析了FGSM攻击原理,提供了从环境搭建、模型准备到攻击实施、可视化对比的完整代码实战流程,并探讨了对抗样本的生成与模型鲁棒性评估。

3a9bq4r8t2y的博客 216

配对堆 数据结构说解

一个配对堆是一棵满足堆序性质的树,树上每个节点保存其关键字、子节点指针、兄弟节点指针和前驱指针。当该节点为其父节点的第一个儿子,即父节点的子节点指针指向该节点时,该节点的前驱指针指向其父节点;否则该节点的前驱指针指向该节点的前一个兄弟,即该节点的前驱指针指向的节点的兄弟节点指针指向该节点。

Foliciatarier的博客 1434

Mac版Burp Suite专业版破解与汉化完整指南

在Web安全测试领域,Java应用程序的许可证验证机制与本地化实现是常见的技术课题。其原理通常涉及Java Agent技术,通过在JVM启动时动态修改字节码,实现对软件授权状态的校验逻辑进行拦截与重写,从而绕过官方验证。同时,软件界面的国际化(i18n)与本地化(l10n)则依赖于资源文件的替换与加载。从技术价值角度看,深入理解这些机制不仅能满足特定场景下的工具定制需求,更能加深对Java安全机制、软件保护与逆向工程的理解。在实际的工程实践中,安全研究人员与开发者常面临功能受限或语言障碍的挑战,尤其是在渗透

weixin_29327525的博客 1045

[数据结构] 配对堆

模板链接:https://www.luogu.org/problemnew/show/P3371 【模板】堆 题目描述 如题,初始小根堆为空,我们需要支持以下3种操作: 操作1: 1 x 表示将x插入到堆中 操作2: 2 输出该小根堆内的最小数 操作3: 3 删除该小根堆内的最小数 输入输出格式 输入格式: 第一行包含一个整数N,表示操作的个数 接下来N行,每行包含...

ShadyPi的IT笔记 905

VS2015+Qt+OpenCV+open62541开发过程(05_VS2015中使用OPCUA open62541通过Kepware读写PLC数据)

此外还需要所需lib包:WS2_32.Lib(opcua通讯用到的库,opcua使用socket通讯,需使用此库),添加#pragma comment(lib,"ws2_32.lib") 语句,有的文章说需要添加ws2_32.lib文件或ws2_32.dll到项目资源中,貌似影响不大。2.在VS中选择x64平台会报错,选择x86平台可以用,应该是ws2_32.lib相关的库不支持64位,目前没有解决,继续后面的实验,看看用Qt后是否可以解决。6.VS中建立的变量,与读取的OPC点数据类型注意对应。

zzh17的博客 1862

lammps 案例:高熵合金FeNiCrCoCu建模及拉伸模拟

高熵合金力学性能模拟包括拉伸、剪切、摩擦等模拟,本文以高熵合金FeNiCrCoCu为例介绍高熵合金建模及拉伸过程模拟。 FeNiCrCoCu原子数量比为1:1:1:1:1 高熵合金的拉伸与单晶拉伸过程类似,主要包含以下步骤: 1.建模 高熵合金含有原子类型较多,建模时,一般以含量最多的原子为主建立初始模型。 使用set type/ratio或者set type/fraction的方式按一定比例替换为其它合金原子。 如随机选择20%的原子1,将其转换为原子2: set type 1 type/ratio 2

lammps_jiayou的博客 6067

【模板】【数据结构】【堆】配对堆(合并堆)

//配对堆:pdd //luogu P3377 【模板】左偏树 //选择使用数组结构,故而不用链表去记录右兄弟,直接记录儿子们 #include<cstdio> #include<cstdlib> #include<algorithm> #include<queue> using namespace std; int ...

dimei7686的博客 384

Spring Boot 集成 Spring AI:实现可被大模型调用的 MCP Server

本文介绍了如何利用Spring AI框架构建MCP Server,使大模型能够调用业务系统的接口。主要内容包括:1)MCP协议简介,分为MCP Server和Client两部分;2)环境配置要求,需Java 17+和Spring Boot 3.5.0+;3)实现步骤,通过@McpTool注解将Service方法暴露给大模型调用;4)使用Cherry Studio作为MCP Client测试效果,成功实现订单查询和下单功能。本文仅实现MCP Server部分,后续将介绍如何开发MCP Client。

iioSnail的博客 2127

【数据结构】 配对堆&BZOJ 3040

【引子】 (BZOJ3040) 有一个m边有向图,节点从1~n编号,求从1到n的最短路。 n≤106,m≤107n \leq 10^6,m \leq 10^7n≤106,m≤107 显然是一个dijkstra+堆优化的板题。 然而一般的堆会被卡空间。 所以我们需要一个更高效的堆。 网上找了半天,可选的数据结构好像只有斐波那契堆和配对堆。然而前者好难写啊 ε=(´ο`*)))唉。 接下来介绍一下配...

C20181503csy的博客 454

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【算法竞赛学习笔记】配对堆与左偏树-超好懂的数据结构!!!

title : 配对堆与左偏树 date : 2021-8-22 tags : 数据结构,ACM 配对堆 简介 配对堆(pairing heap),支持插入,查询/删除最小值,合并,修改等操作,跑得比较快,但不能可持久化。 操作 合并 直接把根节点权值较大的那个配对堆设成另一个的儿子即可。 插入 新元素可视为一个配对堆,与原堆合并即可。 删除最小值(复杂) 若把根节点的儿子都一个个合并到一起,复杂度会退化到O(n) Eileen保证均摊复杂度为O(logn),我们需要把儿子从左往右两两配对。 Node.

SC_Linno的博客 643

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

数据结构与算法分析:配对堆

配对堆

weixin_52812620的博客 461

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

《数据结构与算法分析》第十二章,K-d树,与配对堆简要介绍与实现

前言:        这里我要介绍的这4个数据结构,是在《数据结构与算法分析》一书上的最后4种数据结构了。这些数据结构给出来了实现的代码,实现的难度并不大,一天之内我就把这四个数据结构的测试代码给调通了。        这四个数据结构里,一个是红黑树的变种,对红黑树进行了化简,一个是为了多维查询范围所设计的数据结构。最后一个是变种的斐波那契堆,目的同样是为了化简实现。本来我想这

yw8355507的专栏 633

【刷题】【数据结构】【堆】配对堆 [APIO2012] 派遣

这一题中各位忍者的关系其实是一棵树: b[i]是他的上司,也是他的父节点 一个父节点可能有多个儿子 父子关系就是命令的传达关系,父亲能传给儿子 所以如果选中一个管理者,那他就是这棵树的根 因为忍者只要祖宗是管理者,就可以传话, 所以管理者可以选择的忍者的范围就是他的所有子孙 而传话的忍者也可以不被调遣,管理者也可以 所以选择忍者时,不用要求这个被选择点的父亲也被选择 那...

dimei7686的博客 156

配对堆实现文件C语言

<br />/* PairingHeap.c -- 配对堆实现文件 */ #include "PairingHeap.h" /* 全局变量声明 */ static Node * NullNode = NULL ; /* 局部函数声明 */ static Node * compareAndLink_P (Node * const first, Node * const second) ; static Node * makeNode_P (const Item item) ; s

让阳光照进心里 1437

浅谈堆(Heap)

Heap定义二叉堆 定义 堆是一颗特殊的树,树中每个节点的值都 大/小大 / 小大/小 于其子节点。 每个节点值大于其子节点的,被称为大根堆。 每个节点值小于其子节点的,被称为小根堆。 我们习惯性将 二叉堆二叉堆二叉堆 简称为 堆堆堆。 也因此,在许多地方对堆都有这样的描述: 堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆(Heap)是计算机科学

NOW I GOT ONE WAY 843

配对堆头文件C语言

<br />  这个东西, 书上没有给出时间分析.我自己也思考了下,想给出很上的界是能的,但是确切的时间,很难给出,这东西完全是根据操作序列来产生各种高度,所以很难给出具体的界.<br />  Insert () O (1).<br />  DecreaseKey () O (1).<br />  DeleteMin () ,就不知道了.<br />  好吧,贴代码了.<br />/* PairingHeap.h -- 配对堆头文件 */ #include <stdio.h> #include <std

让阳光照进心里 2317

数据结构——堆

堆是一棵树,其每个节点都有一个键值,且每个节点的键值都大于等于/小于等于其父亲的键值。每个节点的键值都大于等于其父亲键值的堆叫做小根堆,否则叫做大根堆。STL 中的 priority_queue 其实就是一个大根堆。插入一个数、查询最小值、删除最小值、合并两个堆、减小一个元素的值。一些功能强大的堆(可并堆)还能(高效地)支持merge等操作。一些功能更强大的堆还支持可持久化,也就是对任意历史版本进行查询或者操作,产生新的版本。

m0_61360607的博客 1864
上一篇: 数据结构——堆
下一篇: 题目:售货员的难题(状压dp)
GG_Bond...
博客等级 码龄5年 531粉丝 64原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值