首页 > 其他分享 >【学习笔记】二叉堆

【学习笔记】二叉堆

时间:2022-11-26 21:01:33浏览次数:54  
标签:log int 笔记 二叉 学习 heap 操作 节点

二叉堆

在谈二叉堆之前,先来叙述一下堆的概念。

堆,听起来可能与栈的结构差不多(?),但是堆其实是一棵树。这棵树的根就是堆顶,一般堆顶就是用来维护答案的(e.g. 最大值、最小值)。

如果根节点是最大值,则称该堆为最大堆(大根堆);如果根节点是最小值,则称该堆为最小堆(小根堆)。

堆一般用二叉树实现,称为二叉堆。

二叉堆的典型应用:堆排序与优先队列。

二叉堆的概念

二叉堆本质上是一棵完全二叉树。用数组实现的二叉堆,书中的每个节点与数组中存放的每个元素相对应。

完全二叉树指的就是这棵二叉树的每层,除了最后一层可能不是满的,其他都是满的。

上图(图一)为用数组实现二叉堆的示意图。

二叉堆中的每个节点,都是以它为根节点的子树的最值。

用数组 \(f\) 储存这棵完全二叉树,节点数量为 \(n\),\(f_1\) 为根节点,则有以下性质:

  1. 对于一个 \(i > 1\) 的节点,其父亲节点位于 \(i \div 2\)。
  2. 如果 \(2 \times i > n\),那么节点 \(i\) 没有儿子;如果 \(2 \times i + 1 > n\),那么节点 \(i\) 没有右儿子。
  3. 如果节点 \(i\) 有儿子,则它的左儿子位于 \(2 \times i\),右儿子位于 \(2 \times i + 1\)。

堆的操作十分简单,只有进堆和出堆。

  • 进堆:每次都把元素插进堆里,只不过要保证插入完成后该堆依旧是一棵完全二叉树,插入完成后,调整堆的形态,保持堆符合最大堆或最小堆的特点。
  • 出堆:即删除根节点。删除根节点后调整堆的形态,保持堆符合最大堆或最小堆的特点。

二叉堆的操作

在 【二叉堆的概念】提到了堆的两种操作。

其实这两种操作准确的来讲是上浮与下沉。

上浮

定义:如果这个节点的权值大于它父亲的权值,就交换,重复此过程直到不满足或者到根。

堆的进堆过程中,插入操作最简单的方式就是把元素插入到最低层的最右侧。若最底层已满,则新建一层。

上图(图二)为最大堆的上浮过程示意图。

可以证明,插入之后向上调整后,没有其他节点会不满足堆性质。

时间复杂度:\(O(\log n)\)。

下沉

定义:在该节点的儿子中,找一个最大的,与该节点交换,重复此过程直到底层。

我们不妨想象,直接删除根节点的话,就会变成两个堆,很难处理。

所以不妨考虑插入操作的逆过程,设法将根节点移到最后一个节点,然后直接删掉。
然而实际上不好做,我们通常采用的方法是,把根节点和最后一个节点直接交换。

但是在交换节点后,如果直接删除交换后的根节点的话,堆将会不满足堆性质。

可以证明,删除并向下调整后,没有其他节点不满足堆性质。

应为上文已经出示过上浮的示意图,所以这里不在展示下沉的示意图了。(下沉就是上浮的逆操作)

时间复杂度:\(O(\log n)\)

更改某个点的权值

如果是减小的话,那么直接将该节点上浮就可以了。

如果是增大的话,那么直接将该节点下沉就可以了。

时间复杂度:\(O(\log n)\)

实现

根据上文,我们知道二叉堆主要依靠的就是上浮与下沉操作。

所以我们根据完全二叉树的性质,考虑使用数组 \(h_i\) 表示该堆,即 \(h_{2i}\) 表示 \(i\) 的节点的左儿子,\(h_{2i + 1}\) 表示 \(i\) 节点的右儿子。\(1\) 是根节点。

上浮

void up(int x)
{
    while (x > 1 && h[x] > h[x / 2])
    {
        swap(h[x], h[x / 2]);
        x /= 2;
    }
}

下沉

void down(int x)
{
    while (x * 2 <= n)
    {
        t = x * 2;
        if (t + 1 <= n && h[t + 1] > h[t])
            t ++;
        if (h[t] <= h[x])
            break;
        swap(h[x], h[t]);
        x = t;
    }
}

建堆

建堆其实可以想象成 \(n\) 个节点依次插入堆。

所以就有两种操作:

  • 一:向上调整
  • 二:向下调整

方法一

从根开始,按照 BFS 序依次插入进堆。然后进行向上调整操作。

void BuildHeap()
{
    for (i = 1; i <= n; i ++)
        up(i);
}

总复杂度:
\(\log 1 + \log 2 + \log 3 + \cdots + \log n = O(n \log n)\)。

方法二

从叶子开始,依次进行向下调整操作。

void BuildHeap()
{
    for (i = n; i >= 1; i --)
        down(i);
}

如此做就相当于:每次合并两个已经调整好的堆(即满足堆性质的堆),这已经说明该算法的正确性。

我们可以看出这种方式的复杂度为 \(O(\log n - k)\)。另外假如题目数据较紧,我们可以想:叶子节点无需调整,所以只需要从序列约 \(\dfrac{n}{2}\) 的位置开始向下调整即可。

总复杂度:
\(n \log n - \log 1 - \log 2 - \log 3 - \cdots - \log n\) = \(O(n)\)。

具体过程见 OI Wiki

模板题

Description

现有一个空的小根堆,并有以下三种操作:

  1. 输入 1 x,表示将 \(x\) 插入堆中。
  2. 输入 2,则输出该小根堆内的最小数。
  3. 输入 3,表示删除该小根堆内的最小数。

Input

第一行一个整数 \(n\),表示操作个数。

接下来 \(n\) 行,每行 \(1\) 或 \(2\),个正整数,表示进行的操作。

Output

对于每个操作 2,输出一个整数表示答案(每两个答案之间空一行)。

Limit

\(n \le 10^6\),保证操作是三种操作之一。

Code

下面给出代码,读者可以根据上文自行理解。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n, len;
int heap[maxn];

void up(int x)
{
    heap[++ len] = x;
    int i = len;
    while (i > 1 && heap[i] < heap[i / 2])
    {
        swap(heap[i], heap[i / 2]);
        i = i / 2;
    }
}

void down()
{
    heap[1] = heap[len --];
    int i = 1;
    while (2 * i <= len)
    {
        int son = 2 * i;
        if (son < len && heap[son + 1] < heap[son])
            son ++;
        if (heap[son] < heap[i])
        {
            swap(heap[son], heap[i]);
            i = son;
        }
        else
            break;
    }
}

int main()
{
    int n;
    scanf("%d", &n);
    while (n --)
    {
        int opt;
        scanf("%d", &opt);
        if (opt == 1)
        {
            int x;
            scanf("%d", &x);
            up(x);
        }
        else if (opt == 2)
            printf("%d\n", heap[1]);
        else
            down();
    }
}

参考文献

标签:log,int,笔记,二叉,学习,heap,操作,节点
From: https://www.cnblogs.com/mrCrazyWolf/p/16928285.html

相关文章

  • C语言学习笔记---大小端
    大小端的原理对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端字节序;另一种方法是将高序字节存储在起始地址,......
  • 2022-2023-1 20221404 《计算机基础与程序设计》第十三周学习总结
    2022-2023-120221404《计算机基础与程序设计》第十三周学习总结作业信息班级链接(2022-2023-1-计算机基础与程序设计)作业要求(2022-2023-1计算机基础与程序设......
  • Java学习七
    一.小结1.使用二维数组来存储表格2.可以使用以下语法来声明二维数组变量:元素类型[][]数组变量3.可以使用以下语法来创建二维数组变量:new元素类型[行的个数][列的......
  • #yyds干货盘点# 动态规划专题:加分二叉树
    1.简述:描述设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有......
  • 数据结构学习总结
    以下内容来自网课数据结构与算法基础(青岛大学-王卓),结合一些博客作出总结,代码来自网络与一些书籍相互交流,共同进步目录数据结构基本概念与术语数据结构结构逻辑结构和......
  • 洛谷P2015 二叉苹果树
    slojP10153.「一本通5.2例1」二叉苹果树P2015二叉苹果树题目描述有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)这棵树共有N个结点(叶子......
  • 2022-2023-1 20221307《计算机基础与程序设计》第十三周学习总结
    作业信息这个作业属于那个班级: https://edu.cnblogs.com/campus/besti/2022-2023-1-CFAP作业要求: https://www.cnblogs.com/rocedu/p/9577842.html#WEEK13作业目标:学习......
  • 深度学习 花书 pdf 全文内容分享
    pan.baidu.com/s/1BzSONl5kpYJazc60g1sX-Q?pwd=r5tg深度学习是一本为深度学习领域奠定基础的经典教科书,由伊恩·古德费罗、约舒亚·本吉奥和阿龙·库维尔三位世界著名专家......
  • 有监督学习神经网络的回归拟合——基于红外光谱的汽油辛烷值预测附Matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进。......
  • ClickHouse 学习
    ClickHouse是新生代的OLAP,尝试使用了很多有趣的实现,虽然仍旧有很多不足,比如不支持数据更新、动态索引较差、查询优化难度高、分布式需要手动设计等问题。但由于它架构简单......