堆(堆排序 模拟堆)

news/2024/10/20 3:55:29/

目录

  • 一、堆的数据结构
  • 二、堆的操作方法
    • 往下调整的示意图
    • 往上调整的示意图
    • 相关功能的实现思路
      • 1.插入一个数
      • 2.求最小值
      • 3.删除最小值
      • 4.删除任意一个元素
      • 5.修改任意一个元素
  • 三、堆的实战运用
    • 堆排序
    • 模拟堆


一、堆的数据结构

堆是一个完全二叉树:除了最后一层结点以外,上面的每一层都是满的。最后一层的结点是从左到右排布的。
堆的数据结构

小根堆:每一个点都是小于左右子节点的,所以根节点就是树中最小值或者叫小顶堆。(递归定义)

存储方式:全新的存储方式,用一维数组来存。因为是完全二叉树,所有数据的下标是有规则可以找到的。

  • 父节点x/2
  • 节点x
  • 左子节点2x
  • 右子节点2x+1

注意下标从1开始的(符合节点之间的数学规律)


二、堆的操作方法

往下调整的示意图

down(x) 往下调整
往下调整

往上调整的示意图

up(x) 往上调整
往上调整

相关功能的实现思路

1.插入一个数

heap[++size] = x;
up(sz); // 往上调整

2.求最小值

heap[1];

3.删除最小值

heap[1] = heap[size--];
down(1);

4.删除任意一个元素

heap[k] = heap[sz--];
// 只执行其中之一 :大了向下走  小了向上走
down(k);
up(k);

5.修改任意一个元素

heap[k] = x;
down(k);
up(k);

三、堆的实战运用

堆排序

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

输入格式:
第一行包含整数 n n n m m m。第二行包含 n n n 个整数,表示整数数列。

输出格式:
共一行,包含 m m m 个整数,表示整数数列中前 m m m 小的数。

数据范围:
1 ≤ m ≤ n ≤ 1 0 5 1≤m≤n≤10^5 1mn105

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

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

tip:
tip
当建树时应从 n / 2 n/2 n/2的位置开始向上进行down(x)操作,因为根节点无需往下调整,同时其时间复杂度为 O ( n ) O(n) O(n)

由于根节点为堆的最小值,所以最后询问操作时,打印一次堆最小值,同时删除。

除此之外使用 for (int i = 1; i <= n; i++) up(i);时间复杂度为 o ( n l o g n ) o(nlogn) o(nlogn)的这种方法也是可以的。

由于堆为完全二叉树,其高度为 l o g 2 n log_2n log2n层,递归版的down(x)的时间复杂度为 o ( l o g n ) o(logn) o(logn),所以没必要将down(x)改为循环。


代码实现:

const int N = 1e5 + 10;
int h[N], cnt;
void down(int x)
{int t = x;// 注意此处不能使用else if// 每次左右子节点要分别进行一次条件判定if (x * 2 <= cnt && h[x * 2] < h[t]) t = x * 2;if (x * 2 + 1 <= cnt && h[x * 2 + 1] < h[t]) t = x * 2 + 1;if (x != t){swap(h[x], h[t]);down(t);}
}
int main()
{cin.tie(0);int n, m;cin >> n >> m;for (int i = 1; i <= n; ++i) cin >> h[i];cnt = n;for (int i = n / 2; i > 0; --i) down(i); //建树,从倒数第二层开始建,然后向上while (m--){cout << h[1] << ' ';h[1] = h[cnt--];down(1);}return 0;
}

tip:
up(x)的基础操作:

①循环

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

②递归

void up(int x)
{if(x / 2 && h[x / 2] > h[u]){swap(h[x / 2], h[x]);up(x / 2);}
}

down(x)循环版本:

void down(int i) {while ((i << 1) <= sz) {int t = i << 1;if (t + 1 <= sz && h[t] > h[t + 1]) t ++;if (h[t] >= h[i]) break;swap(h[t], h[i]);i = t;}
}

模拟堆

题目描述:

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

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

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

输入格式:
第一行包含整数 n n n

接下来 n n n 行,每行包含一个操作指令,操作指令为 I xPMDMD kC k x 中的一种。

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

每个结果占一行。

数据范围:
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105

− 1 0 9 ≤ x ≤ 1 0 9 −10^9≤x≤10^9 109x109

数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6

难点:
要找到第 k k k 个插入的数,因为插入后会进行上下调整,要额外开辟数组,来储存第 k k k 个插入的数在堆中的位置。


代码实现:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;const int N = 1e5 + 10;// h代表heap(堆),ph(point->heap)可以获得第几个插入的元素现在在堆的那个位置
// hp(heap->point)可以获得在堆的第n个元素存的是第几个插入的元素
int h[N], ph[N], hp[N], cnt;void heap_swap(int a, int b)
{// 根据a和b的位置找到它们分别是第几个插入的元素,然后将其(在h数组中的)下标转换swap(ph[hp[a]], ph[hp[b]]);// 将两个位置存的是第几号元素转换swap(hp[a], hp[b]);// 最后再转换值(这三个语句位置可以换,但是从上到下逐渐变短的话比较美观)swap(h[a], h[b]);
}
void down(int x)
{int t = x;if (2 * x <= cnt && h[2 * x] < h[t]) t = 2 * x;if (2 * x + 1 <= cnt && h[2 * x + 1] < h[t]) t = 2 * x + 1;if (t != x){heap_swap(x, t);down(t);}
}
void up(int x)
{while (x / 2 && h[x] < h[x / 2]){heap_swap(x, x / 2);x >>= 1;}
}int main()
{cin.tie(0);int n, m = 0;cin >> n;while (n--){string s;int k, x;cin >> s;if (s == "I"){cin >> x;cnt++;m++;ph[m] = cnt, hp[cnt] = m;h[cnt] = x;up(cnt);}else if (s == "PM") cout << h[1] << endl;else if (s == "DM"){heap_swap(1, cnt);cnt--;down(1);}else if (s == "D"){cin >> k;k = ph[k];heap_swap(k, cnt);cnt--;up(k);down(k);}else if (s == "C"){cin >> k >> x;k = ph[k];h[k] = x;up(k);down(k);}else cout << "error" << endl;}return 0;
}

http://www.ppmy.cn/news/96625.html

相关文章

Office project 2010安装教程

哈喽&#xff0c;大家好。今天一起学习的是project 2010的安装&#xff0c;Microsoft Office project项目管理工具软件&#xff0c;凝集了许多成熟的项目管理现代理论和方法&#xff0c;可以帮助项目管理者实现时间、资源、成本计划、控制。有兴趣的小伙伴也可以来一起试试手。…

全网最全最有用的网络安全学习路线!整整一晚上才整理出来!

正文&#xff1a; 废话不多说&#xff0c;先上一张图镇楼&#xff0c;看看网络安全有哪些方向&#xff0c;它们之间有什么关系和区别&#xff0c;各自需要学习哪些东西。 在这个圈子技术门类中&#xff0c;工作岗位主要有以下三个方向&#xff1a; 安全研发安全研究&#xff1…

openGauss 3.1企业版升级至5.0

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

网络安全术语

网络安全术语 AC Access Controller&#xff0c;接入控制器 AP Access Point&#xff0c;接入点 ASG Application Security Gateway&#xff0c;应用安全网关 BFD Bidirectional Forwarding Detection&#xff0c;双向转发检测 BGP Border Gateway Protocol&#xff0c;边…

单双精度浮点数的存储

//int main() //{ // float f 5.5; // //5.5 // // 101.1 二进制 // // (-1)^0 * 1.011*2^2 // // s 0 M 1.011 E 2 // // E存的是2 127 129 // // 0 10000001 011 00000000000000000000000 // // s E M // // 4 0 …

HTML <em> <strong> <dfn> <code> <samp> <kbd><var> <cite> 标签

定义和用法 以下元素都是短语元素。虽然这些标签定义的文本大多会呈现出特殊的样式,但实际上,这些标签都拥有确切的语义。 我们并不反对使用它们,但是如果您只是为了达到某种视觉效果而使用这些标签的话,我们建议您使用样式表,那么做会达到更加丰富的效果。 ">&…

性能优化概述

一见如故 Lighthouse跑分&#xff1a;点加号&#xff08;左上角&#xff09; > 设备选桌面、类型选性能、设置&#xff08;右上角&#xff09;选用去缓存LCP(Largest Contentful Paint) 最大内容呈现到屏幕上的时间&#xff08;多指图片、视频&#xff09;FID(First Input …

从零开始手搓一个STM32与机智云的小项目——硬件介绍

文章目录 前言硬件简介选型1.主控2.电源3.电机驱动4.舵机驱动5.USB转TTL6.其他模块 原理图绘制1.STM32最小系统1.电源输入2.晶振选择3.复位电路4.BOOT选择电路5.下载电路 2.电源部分及与PC通信部分3.功能模块的实现1.串口2.定时器输入捕获与输出比较3.硬件SPI4.ADC5.温湿度传感…