数据结构与算法学习笔记----堆

news2024/12/26 23:28:28

数据结构与算法学习笔记----堆

@@ author: 明月清了个风
@@ first publish time: 2024.12.2
@@ revised: 2024.12.3 - 例题标题错误,已修改。

ps⛹从这里开始调整了文章结构,先讲解算法和数据结构基本原理,再给出例题,针对例题中的应用再讲解思路。

堆是一种完全二叉树,常用于实现优先队列(priority_queue)等功能,可以根据堆内元素关系分为大根堆小根堆

  • 大根堆:每个父节点的值都大于或等于其子节点的值,根节点是堆中最大的元素。
  • 小根堆:每个父节点的正都小于或等于其子节点的值,根节点是对重最小的元素。

对于堆来说(以小根堆为例),常用的操作有建堆、取出最小元素、删除最小元素、删除某个元素、修改某个元素

  1. 建堆O(n)

    堆化(heapify)是堆数据结构中的一个核心操作。建堆的过程其实也是一个排序的过程,以小根堆为例,在读入所有的元素之后进行堆化,使用到的操作是下沉(down),步骤如下:

    • 假设当前节点是最大值
    • 然后比较左子节点和右子节点,找到三个节点中最小的一个。
    • 如果当前节点不是最小值,就与最小值节点交换,并递归下沉被交换的元素,这个数所在的新的子树中仍可能不满足堆的条件。

    这里给出的示例代码使用的是数组的存储方式(当然也可以用链表进行存储)

    从下标1开始存储,左子节点是2 * i,右子节点是2 * i + 1

    每次对于一个节点,在其作为根节点存在的子树中找到最小的节点(也就是在i, 2 * i, 2 *i + 1中的最小值)放到这颗子树的根节点上,最后递归被交换的点。

    ⭐️这里有个注意点:建堆的循环中从cnt / 2开始(如果是从0开始存的,就是从cnt / 2 - 1开始)。

    解释:在完全二叉树中,叶子节点的索引cnt / 2 + 1cnt,从cnt / 2 + 1开始的所有节点都是叶子节点,那么对于叶子节点而言,他们自身满足堆的性质,在以他们自己为根节点的子树中是最小值(因为他们也没有子节点和他们对比了),因此不需要调整他们的位置。对于非叶子节点而言,需要进行对比确保其子树堆的性质,因此可以从cnt / 2开始遍历。

    int h[N], cnt;
    
    void down(int u)
    {
        int t = u;
        if(u * 2 <= cnt && h[u* 2] < h[u]) t = u * 2;
        if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[u]) t = u * 2 + 1;
        if(u != t)
        {
            swap(h[u], h[t]);
            down(t);
        }
    }
    
    int main()
    {
        
        for(int i = 1; i <= cnt; i ++) cin >> h[i];
        
        for(int i = cnt / 2; i ; i --) down(i);
        
    }
    
  2. 取最小值 O(1)

    取堆中的最小值很简单,返回第一个元素h[1]即可

  3. 删除最小值 O( l o g ( n ) log(n) log(n))

    对于数组存储的二叉树,删除最小值也很简单,只需要用树的最后一个元素将其覆盖h[1] = h[cnt --],再进行一次排序down(1),在最坏情况下, 这个树会沿着树的高度一路down到最底层,因此时间复杂度为O( l o g ( n ) log(n) log(n))。

  4. 删除某个元素

    对于删除某个元素而言,这里考虑的同样是删除最小值的操作,若要删除编号为k的元素,则用树的最后一个元素将其覆盖h[k] = h[cnt --]后,再进行一次堆化排序操作down(k),时间复杂度也和上述操作相同。

  5. 修改某个元素

    修改元素一般只需要将这个值改掉h[k] = x,; cnt ++再堆化一遍donw(k)就行。

Acwing 838.堆排序

[原题链接](838. 堆排序 - AcWing题库)

输入一个长度为 n n n的整数数列,从小到大输出前 m m m小的数。

输入格式

第一行输入整数 n n n m m m

第二行包含 n n n个整数,表示整数数列。

输出格式

共一行,包含 m m m个整数,表示整数数列中前 m m m小的数。

数据范围

1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^{5} 1n,m105,

1 ≤ 数列中元素 ≤ 1 0 9 1 \leq 数列中元素 \leq 10^9 1数列中元素109

代码

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int h[N], cnt;

void down(int u)
{
    int t = u;
    if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if(u != t)
    {
        swap(h[u], h[t]);
        down(t);
    }
}

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++) cin >> h[i];
    
    cnt = n;
    
    for(int i = n / 2; i; i --) down(i);
    
    while(m --)
    {
        cout << h[1] << ' ';
        h[1] = h[cnt --];
        down(1);
    }
    cout << endl;
    
    return 0;
}

Acwing 839.模拟堆

[原题链接](839. 模拟堆 - AcWing题库)

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数x;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第k个插入的数。
  5. C k x,修改第k个插入的数,将其变为x;

现在要进行N次操作,对于所有第 2 2 2个操作,输出当前集合的最小值。

输入格式

第一行输入整数 N N N

接下来 N N N行,每行包含一个操作指令。

输出格式

对于每个输出指令PM,输出一个结果,表示当前集合中的最小值。

数据范围

1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^{5} 1N105,

− 1 0 9 ≤ x ≤ 1 0 9 -10^9 \leq x \leq 10^9 109x109

思路

这道题中的操作都是上面讲过的基本操作,需要注意的地方是位置的映射,因为指定要删除的元素编号是按插入的顺序计算的。我们建堆完成后,数组内元素的顺序就不同了,因此需要保存这个数据,如果是用结构体存储的话相对会更简单,这里给个示例,具体实现就不写了(如果后面有碰或者大家有需要再补吧),需要修改第 k k k个元素就遍历一遍堆找到insertOrderk的元素即可。

typedef struct HeapNode {
    int value;                  // 节点的值
    int insertionOrder;         // 插入顺序,表示该节点是第几次插入堆中的
    struct HeapNode* left;      // 指向左子节点的指针
    struct HeapNode* right;     // 指向右子节点的指针
    struct HeapNode* parent;    // 指向父节点的指针
} HeapNode;

由于使用的是数组存储元素,因此额外的信息(也就是插入顺序)需要另外开数组进行存储,并且因为会涉及到堆中两个元素值的互换,那么插入顺序的索引也需要进行互换,因此开两个数组ph[N]hp[N]

前者ph[k]的含义是第 k k k个插入的元素在堆中的索引,也就是要找到第 k k k个插入元素在堆中排序后的位置操作是k = ph[k]

后者hp[k]的含义是堆中索引为k的元素是第几个插入的。

这里比较绕,画个图解释一下
在这里插入图片描述

图中右侧为一个堆,节点中的数字表示其在堆中排序后的位置(即数组h),现有两个节点ab需要交换位置。

假设a是第一个插入的元素,b是第三个插入的元素,那么对于节点a来说ph[1] = 4,hp[4] = 1,对于节点b来说ph[3] = 2, hp[2] = 3

那么交换时,不仅要交换值,还要交换索引,因此交换操作的代码为

void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

解释:对于要交换的节点ab,首先交换其插入的索引swap(ph[hp[a]], ph[hp[b]])hp[a]hp[b]分别是两节点在堆中的索引,然后交换两者在树中位置的索引swap(hp[a], hp[b]),最后交换两者的值swap(h[a], h[b])

代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100010;

int n, m;
int h[N], ph[N], hp[N], cnt;

void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if(u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

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

int main()
{
    cin >> n;
    
    while(n --)
    {
        char op[5];
        int k, x;
        cin >> op;
        if(!strcmp(op, "I"))
        {
            cin >> x;
            cnt ++;
            m ++;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if(!strcmp(op, "PM")) cout << h[1] << endl;
        else if(!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt --;
            down(1);
        }
        else if(!strcmp(op, "D"))
        {
            cin >> k;
            k = ph[k];
            heap_swap(k, cnt);
            cnt --;
            up(k);
            down(k);
        }
        else
        {
            cin >> k >> x;
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }
    
    return 0;
    
   
}

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

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

相关文章

【C++】格式化输出详解:掌握 cout 的进阶用法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;格式化输出的理论概述&#x1f4af;控制输出宽度和填充字符setw 操作符setfill 操作符 &#x1f4af;控制浮点数的显示格式fixed 与 scientificsetprecision &#x1f4af;…

Rust : 生成日历管理markdown文件的小工具

需求&#xff1a; 拟生成以下markdown管理小工具&#xff0c;这也是我日常工作日程表。 可以输入任意时间段&#xff0c;运行后就可以生成以上的markdown文件。 一、toml [package] name "rust-workfile" version "0.1.0" edition "2021"[d…

Spring01——Spring Framework系统架构

spring学习路线 Spring Framework系统架构 相关概念 低耦合&#xff1a;是指系统中各个模块或组件之间的依赖性较低&#xff0c;即它们之间的联系较少、交互简单。这种设计原则的目的是为了提高系统的灵活性和可维护性&#xff0c;便于模块的独立开发、测试和替换。解耦&…

MicroBlaze软核开发(一):Hello World

实现功能&#xff1a;使用 MicroBlaze软核 串口打印 Hello World Vivado版本&#xff1a;2018.3 目录 MicroBlaze介绍 vivado部分&#xff1a; 一、新建工程 二、配置MicroBlaze 三、添加Uart串口IP 四、生成HDL文件编译 SDK部分&#xff1a; 一、导出硬件启动SDK 二、…

ZLMediaKit+wvp (ffmpeg+obs)推拉流测试

这里使用了两种方式: ffmpeg命令和 OBS OBS推流在网上找了些基本没有说明白的, 在ZLMediaKit的issues中看到了一个好大哥的提问在此记录一下 使用OBS推流&#xff0c;rtmp&#xff0c;报鉴权失败 推流 1. ffmpeg命令推流 官方说明文档地址: 推流规则 rtsp://192.168.1.4:10554…

微信小程序之简单的数据中心管理平台(1)

微信小程序之简单的数据中心管理平台&#xff08;1&#xff09; 引言 随着微信小程序的广泛应用&#xff0c;越来越多的企业开始探索如何利用这一技术开发高效、便捷的管理平台。数据中心管理作为信息化建设的重要组成部分&#xff0c;需要一个灵活、可扩展的界面来实现资源的…

【计算机网络】实验13:运输层端口

实验13 运输层端口 一、实验目的 本次实验旨在验证TCP和IP运输层端口号的作用&#xff0c;深入理解它们在网络通信中的重要性。通过实验&#xff0c;我将探讨端口号如何帮助区分不同的应用程序和服务&#xff0c;使得在同一台主机上能够同时运行多个网络服务而不发生冲突。此…

人工智能学习用的电脑安装cuda、torch、conda等软件,版本的选择以及多版本切换

接触人工智能的学习三个月了&#xff0c;每天与各种安装包作斗争&#xff0c;缺少依赖包、版本高了、版本低了、不兼容了、系统做一半从头再来了。。。这些都是常态。三个月把单位几台电脑折腾了不下几十次安装&#xff0c;是时候总结一下踩过的坑和积累的经验了。 以一个典型的…

数组常见查找算法

文章目录 时间复杂度1. 顺序查找&#xff08;Linear Search&#xff09;2. 二分查找&#xff08;Binary Search&#xff09;3. 插值查找&#xff08;Interpolation Search&#xff09;4.分块查找5.哈希查找 时间复杂度 衡量算法执行时间随输入规模增长而增长的速度的一个概念。…

langgraph 多智能体 Multi-agent supervisor

1. 工具定义 1.1网络搜索工具 from typing import Annotated import os from langchain_community.tools.tavily_search import TavilySearchResults from langchain_core.tools import tool from langchain_experimental.utilities import PythonREPLos.environ["TAVIL…

【Maven系列】深入解析 Maven 镜像配置

前言 Maven 是一个流行的 Java 项目管理和构建工具&#xff0c;可以自动化构建项目、管理依赖、生成报告等。在Maven构建项目时&#xff0c;通常经常需要下载各种依赖。默认情况下&#xff0c;Maven 会从中央仓库下载这些依赖&#xff0c;但在某些情况下&#xff0c;这个过程可…

HTML 添加 文本水印

body,html {margin: 0;height: 100vh;width: 100vw;} // 自定义文案const setting {text: "水印文案", // 水印内容innerDate: true, // 在水印下方增加日期width: 110, // 水印宽度};// 自定义文字水印const watermark (function () {return {build: function (a…

华为HCIE-Datacom认证笔试+实验考试介绍

华为HCIE数通认证考试是面向那些希望成为数通网络领域专家的人员&#xff0c;考试通常两部分&#xff1a;笔试和实验考试。 考试科目&#xff1a; HCIE-Datacom笔试考试内容&#xff1a; HCIE-Datacom V1.0考试覆盖数据通信领域路由交换高阶技术、企业网络架构全景、园区网络…

PyCharm+Selenium+Pytest配置小记

1、下载ChromeDriver&#xff1a; Chrome130以后的Driver下载&#xff1a; Chrome for Testing availabilityhttps://googlechromelabs.github.io/chrome-for-testing/ &#xff08;1&#xff09;查看自己Crome浏览器的版本&#xff1a;设置-->关于 Chrome&#xff1b; &…

我们来学webservie - WSDL

WSDL 题记WSDL系列文章 题记 举个例子 酒桌上大领导们谈笑风生&#xff0c;把酒临风,其喜洋洋者矣老张说能签下xx项目&#xff0c;一来证明了集团在行业中的翘楚地位&#xff0c;二来感谢各位领导给予的大力支持接下来的一周&#xff0c;项目经理、业务顾问相继入场&#xff0…

weblogic开启https

JSK证书生成 生成密钥库和证书 使用Java的keytool命令来生成一个Java密钥库&#xff08;Keystore&#xff09;和证书。keytool是Java开发工具包&#xff08;JDK&#xff09;中用于管理密钥库和证书的命令行工具。 #创建证书存放目录 [weblogicosb1 jksHL]$ mkdir -p /home/w…

激活函数在神经网络中的应用与选择

目录 ​编辑 Sigmoid函数 代码示例与分析 Tanh函数 代码示例与分析 ReLU函数 代码示例与分析 Leaky ReLU函数 代码示例与分析 PReLU函数 代码示例与分析 ELU函数 代码示例与分析 SELU函数 代码示例与分析 Softmax函数 代码示例与分析 结论 在深度学习领域&am…

使用Tauri创建桌面应用

当前是在 Windows 环境下 1.准备 系统依赖项 Microsoft C 构建工具WebView2 (Windows10 v1803 以上版本不用下载&#xff0c;已经默认安装了) 下载安装 Rust下载安装 Rust 需要重启终端或者系统 重新打开cmd&#xff0c;键入rustc --version&#xff0c;出现 rust 版本号&…

2023年第十四届蓝桥杯Scratch国赛真题—推箱子

推箱子 程序演示及其源码解析&#xff0c;可前往&#xff1a; https://www.hixinao.com/scratch/creation/show-188.html 若需在线编程&#xff0c;在线测评模考&#xff0c;助力赛事可自行前往题库中心&#xff0c;按需查找&#xff1a; https://www.hixinao.com/ 题库涵盖…

[RabbitMQ] RabbitMQ常见应用问题

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…