首页 > 其他分享 >【数据结构】- 堆

【数据结构】- 堆

时间:2023-10-04 20:15:32浏览次数:34  
标签:Node return log rs ls 数据结构 节点


简介

堆是可以维护 最值 的数据结构。其每个节点有一个键值 \(val\) ,堆将节点按键值确定父亲/儿子关系,故把所有节点连为一棵树,通过根找到最值。

image

根据祖先关系可分为两类——大根堆以根节点键值最大,向叶节点递减。小根堆以根节点键值最小,向叶节点递增。

根据支持操作可分为堆、可并堆、可持久化堆,按包含顺序给出。

细分表格如下:

操作 \ 数据结构 配对堆 二叉堆 左偏树 二项堆 斐波那契堆
插入 \(O(1)\) \(O(\log n)\) \(O(\log n)\) \(O(1)\) \(O(1)\)
查询最小值 \(O(1)\) \(O(1)\) \(O(1)\) \(O(\log n)\) \(O(1)\)
删除最小值 \(O(\log n)\) \(O(\log n)\) \(O(\log n)\) \(O(\log n)\) \(O(\log n)\)
合并 \(O(1)\) \(O(n)\) \(O(\log n)\) \(O(\log n)\) \(O(1)\)
减小一个元素的值 \(o(\log n)\)(下界 \(\Omega(\log \log n)\),上界 \(O(2^{2\sqrt{\log \log n}}\))) \(O(\log n)\) \(O(\log n)\) \(O(\log n)\) \(O(1)\)
是否支持可持久化 \(\times\) \(\checkmark\) \(\checkmark\) \(\checkmark\) \(\times\)

而习惯上,不加限定提到「堆」时往往都指二叉堆。

二叉堆

结构

一棵完全二叉树,每个结点中存有一个权值。

性质

父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。

由堆性质,树根存的是最值,所以可以 \(O(1)\) 查询。

操作

插入即放入右下角后使其上浮,删除即与右下角的节点交换后使其下沉。

实现

STL中的priority_queue已经支持了pushpoptop和通用的sizeempty函数。

可并堆

左偏树

结构

一棵二叉树,每个结点定义有一个 \(\mathrm{dist}\) 值:首先定义 外节点 为左儿子或右儿子为空的节点,定义一个外节点的 \(\mathrm{dist}\) 为 \(1\),一个不是外节点的\(\mathrm{dist}\)为其到子树中最近的外节点的距离加一。空节点的\(\mathrm{dist}\)为 \(0\)。

性质

是一种 可并堆,即具有堆的性质,并且可以快速合并,并且它是「左偏」的——每个节点左儿子的 \(\mathrm{dist}\) 都大于等于右儿子的 \(\mathrm{dist}\)。维护时使左偏树每个节点的 \(\mathrm{dist}\) 都等于其右儿子的 \(\mathrm{dist}\) 加一即可。

需要注意的是,\(\mathrm{dist}\) 不是深度,左偏树的深度没有保证,一条向左的链也是左偏树。故而实际运用时可搭配 并查集 快速查询所在堆堆顶。

操作

构建

把每个节点作为左偏树存入队列,依次取出队头两个堆合并。

合并

合并两个堆时,由于要满足堆性质,先取值较小的那个根作为合并后堆的根节点,然后将这个根的左儿子作为合并后堆的左儿子,递归地合并其右儿子与另一个堆,作为合并后的堆的右儿子。为了满足左偏性质,合并后若左儿子的 \(\mathrm{dist}\) 小于右儿子的 \(\mathrm{dist}\),就交换两个儿子。

//用法:x = find(x), y = find(y); rt[x] = rt[y] = merge(x, y);
int merge(int x, int y) {
    if (!x || !y) return x | y;
    if (val[x] > val[y]) swap(x, y);
    rs[x] = merge(rs[x], y); fa[rs[x]] = x;
    if (dis[rs[x]] > dis[ls[x]]) swap(ls[x], rs[x]);
    dis[x] = dis[rs[x]] + 1; return x;
}
插入

将单个节点视作一棵树即可。

删除
  1. 删除根:清空根节点信息并将其左右儿子合并。
void erase(int x) {
    fa[x] = fa[ls[x]] = fa[rs[x]] = 0;
    ls[x] = rs[x] = dis[x] = rt[x] = 0; 
    rt[ls[x]] = rt[rs[x]] = merge(ls[x], rs[x]);
}
  1. 删除任意节点:此时可能会造成父节点的 \(\mathrm{dist}\) 被更新,所以我们使用pushup函数递归信息至不用更新的祖先节点即可。
void pushup(int x) {
    if (!x) return;
    if (dis[ls[x]] < dis[rs[x]]) swap(ls[x], rs[x]);
    if (dis[x] != dis[rs[x]] + 1)
        dis[x] = dis[rs[x]] + 1, pushup(fa[x]);
}
void delete(int x) {
    int fx = fa[x];
    fa[ls[x]] = fa[rs[x]] = fx;
    int rt = merge(ls[x], rs[x]);
    ls[fx] == x ? ls[fx] = rt : rs[fx] = rt;
    rt[ls[x]] = rt[rs[x]] = fx ? find(fx) : rt;
    fa[x] = ls[x] = rs[x] = dis[x] = rt[x] = 0; pushup(fx);
}
修改

需要将每个节点维护的堆与儿子合并,维护整堆修改懒标记时就要用到pushdown函数。在每次 \(merge()\) 操作和 \(erase()\) 操作时下传标记。以维护加法、乘法懒标记为例:

void pushdown(int x) {
    int m = mul[x], a = add[x]; mul[x] = 1; add[x] = 0;
    val[ls[x]] *= m; add[ls[x]] *= m; mul[ls[x]] *= m;
    val[rs[x]] *= m; add[rs[x]] *= m; mul[rs[x]] *= m;
    val[ls[x]] += a; add[ls[x]] += a; val[rs[x]] += a; add[rs[x]] += a;
}

斜堆

差别

斜堆与左偏树的唯一差别是斜堆不满足左偏性,故而斜堆的合并函数是从下往上在路径上的每个节点交换子树。

int merge(int x, int y) {
    if (!x || !y) return x | y;
    if (val[x] > val[y]) swap(x, y);
    rs[x] = merge(rs[x], y); swap(ls[x], rs[x]);
    dis[x] = dis[rs[x]] + 1; return x;
}

配对堆

结构

一棵满足堆性质的带权多叉树,且通常使用儿子 - 兄弟表示法储存一个配对堆。

struct Node {
    T v; int id;
    Node *child, *sibling;
    Node *father;//指向父/兄
};

操作

合并

套路地,令两个根节点较小的一个为新的根节点,然后将较大的根节点作为它的儿子插入进去。并且一个节点的儿子链表是按插入时间排序的,即最右边的节点最早成为父节点的儿子,最左边的节点最近成为父节点的儿子。

Node* merge(Node* x, Node* y) {
    if (x == nullptr) return y;
    if (y == nullptr) return x;
    if (x->v > y->v) swap(x, y);
    if (x->child != nullptr)
        x->child->father = y;
    y->silbing = x->child;
    y->father = x; x->child = y;
    return x;
}
插入

套路地,将新节点视作一个堆进行合并。

删除

删除根节点后要合并森林为一棵树,先拆散孩子后 从左往右 两两配对合并,再 从右往左 两两配对合并。

Node* breakup(Node* x) {
    if (x == nullptr) return nullptr;
    x->father = nullptr;
    if (x-> sibling == nullptr) return x;
    Node *y = x->sibling, *z = y->sibling;//两个兄弟
    x->sibling = y->sibling = y->father = nullptr;//拆开
    return merge(breakup(z), merge(x, y));
}
Node* erase(Node* x) {
    Node* t = breakup(x->child);
    delete x; return t;
}
修改

减小节点权值,可能需要上浮。所以我们选择将其子树拎出来重新并进去。

Node* decrease(Node *root, Node *x, T v) {
    x->v = v;
    if (x == root) return 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 = x->father = nullptr;
    return merge(root, x);
}

可持久化堆

舍左偏树其谁?
但是可持久化数据结构先鸽了(

标签:Node,return,log,rs,ls,数据结构,节点
From: https://www.cnblogs.com/Arson1st/p/17742652.html

相关文章

  • 数据结构-并查集
    并查集的使用范围:1.合并集合2.查询两元素是否属于同一集合   高级用法:  3.进行集合划分<带权并查集>  4.连通块块数查询&块内元素个数统计<连通图>  5.撤销合并<可持久化并查集>//本文暂不涉及,我还不会并查集基本操作:#definerep(i,n)for(int......
  • 点赞功能改进-Redis数据结构设计
        ......
  • 数据结构之"顺序表"
    前言......
  • 第03章 Python的数据结构、函数和文件
    本章讨论Python的内置功能,这些功能本书会用到很多。虽然扩展库,比如pandas和Numpy,使处理大数据集很方便,但它们是和Python的内置数据处理工具一同使用的。我们会从Python最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你自己的、可重复使用的Python函数。最后,会学习P......
  • 高级数据结构--树状数组
    一维树状数组单点修改-区间查询输入32123120213输出6数据范围对于所有数据,\(1≤n,q≤10^6,∣a[i]∣≤10^6,1≤l≤r≤n,∣x∣≤10^6\)。点击查看代码#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nu......
  • 【数据结构】3.跳表和散列
    1.顺序链表字典1.1字典抽象父类#pragmaonceusingnamespacestd;template<classK,classE>classdictionary{public:virtual~dictionary(){}//返回字典是否为空virtualboolempty()const=0;//返回有多少键值对virtualintsize()co......
  • [数据结构和算法] 堆/优先队列的实现
    预备知识:完全二叉树可以用数组表示:从下标0开始存储数据:左子节点=2*父节点+1,右子节点=2*父节点+2;从下标1开始存储数据:左子结点=2*父节点,右子节点=2*父节点+1;堆:大根堆:父节点的值大于等于左右子节点的值;小根堆:父节点的值小于等于左右子节点的值;......
  • 【数据结构】2.栈和队列
    1.栈1.1栈的抽象父类#pragmaoncetemplate<classT>classStack{public://析构函数virtual~Stack(){}//栈是否为空virtualboolempty()const=0;//栈的大小virtualintsize()const=0;//栈顶元素virtualT&top()=0......
  • 基础数据结构:数组实现的单链表(静态链表)、双链表
    1、单链表(静态链表)以AcWing.826为例,题目要求如下:实现一个单链表,链表初始为空,支持三种操作:向链表头插入一个数;删除第k个插入的数后面的数;在第k个插入的数后插入一个数。现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。注意:题目中第k个插入的数并不是指当......
  • 【数据结构】串
    串串的顺序实现简单的模式匹配算法KMP算法KMP算法的进一步优化串的顺序实现初始化#defineMaxSize50typedefcharElemType;//顺序存储表示typedefstruct{ElemTypedata[MaxSize];intlength;}SString;/***初始化串*/voidInitString(SString*string)......