堆排序、堆的模拟
一、堆的概念测试
1、堆的性质
- 是一颗完全二叉树
- 每个节点的值都大于或等于子节点的值,为最大堆;反之为最小堆
2、堆的存储
一般用数组来表示堆,下标从0开始。则下标为 i 的节点的父节点下标为(i-1)/2
,其左右子节点分别为(2i + 1)
、(2i + 2)
。
下标从1开始 左右节点2i 、 2i+1
i / 2
。
二、堆排序
1、基本思想
利用大顶堆(小顶堆)堆顶记录的是最大(小)关键字这一特性,每次从无序数组中选出最大(最小)值。
1、将待排序序列造成一个最大堆,此时根节点为最大值
2、依次将根节点与待排序序列最后一个元素交换
3、维护从根节点到该元素的前一个节点为最大堆
2、基本操作
down:把数往下沉,从而再次变成堆的操作,要考虑和两个左右子节点比较。
up:底下的数往上走,再次变成堆的操作,只要和父节点比较就可以。
插入一个数:heap[++size], up(size)
删除最小值:把最后一个元素覆盖到堆顶,heap[1] = heap[size], size--, down(1)
删除任意一个元素:heap[k]=heap[size];size--;down(k);up(k)
;dwon和up只会执行一个
修改任意元素:heap[k]=x;down();up()
时间复杂度:nlog n
堆排序:每次取出堆顶后,把最后一个数放到堆顶,进行下沉操作,直到堆最后只剩一个元素为止。
模板
#include<iostream> #include<algorithm> using namespace std; const int N = 100010; int h[N], mySize; int n, m; void down(int u) { int t = u; if (2 * u <= mySize && h[t] > h[2 * u]) t = 2 * u; // 先比较左儿子 if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1]) // 再比较右儿子,如果上面更新了则h[t]是左儿子的值,最终h[t]=3个点中最小的值 t = 2 * u + 1; if (u != t) // 根节点不是最小值,交换,把根节点下沉down { swap(h[u], h[t]); down(t); } } int main() { cin >> n >> m; mySize = n; for (int i = 1; i <= n; i++) scanf("%d", &h[i]); for (int i = n / 2; i; i--) down(i); // 建立初始堆 while (m--) { cout << h[1] << " "; h[1] = h[mySize--]; down(1); // 最后一个数放到堆顶,要下沉操作 } return 0; }
三、模拟堆
模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
I x
,插入一个数 x;PM
,输出当前集合中的最小值;DM
,删除当前集合中的最小值(数据保证此时的最小值唯一);D k
,删除第 k 个插入的数;C k x
,修改第 k个插入的数,将其变为 x;
现在要进行 N次操作,对于所有第 22 个操作,输出当前集合的最小值。
定义两个数组:分别是 hp 和 ph
hp-->
堆数组中下标 -> 第k个插入
heap pointer
ph-->
第k个插入 -> 堆数组中下标
pointer heap
这两个函数是互为反函数的。
当在堆中交换两个元素时,那么第k个插入的数的下标要变化、下标对应的数是第几个也要变化。swap操作传入需要交换的堆数组的下标
void swap_heap(int a, int b) { swap(ph(hp[a]), ph[hp[b]]); // 第k个插入的数的下标变化 swap(hp[a],hp[b]); // swap(h[a],h[b]); // 交换数组响应下标对应的数值 }
#include<iostream> #include<string> using namespace std; const int N = 100010; int h[N], mysize, cnt; // cnt记录第k次插入 int ph[N], hp[N]; // ph[k]: 第k个插入 --> 下标 // hp[k]: 下标k -->第几个插入 // 交换堆中的两个数,那么下标和第几次插入的关系也要变化 void swap_heap(int a, int b) { swap(ph[hp[a]], ph[hp[b]]); swap(hp[a], hp[b]); swap(h[a],h[b]); // 交换数值 } void down(int i) { int t = i; if(2 * i <= mysize && h[2 * i] < h[t]) t = 2 * i; if(2 * i + 1 <= mysize && h[2 * i + 1] < h[t]) t = 2 * i + 1; if(t != i) { swap_heap(t, i); down(t); } } void up(int i) { while(i / 2 && h[i] < h[i / 2]) { swap_heap(i, i / 2); i /= 2; } } // 插入一个数,在最后插入 void insert(int x) { mysize++; // 记录下标 cnt++; // 第几次插入 ph[cnt] = mysize, hp[mysize] = cnt; h[mysize] = x; up(mysize); } // 删除第k个插入数 void del(int k) { k = ph[k]; // 第k个插入的数对应的下标 swap_heap(k, mysize); mysize--; down(k); up(k); } // 删除最小值,堆顶 void Del() { swap_heap(1, mysize); mysize--; down(1); } // 修改第k个插入的数 void change(int k, int x) { k = ph[k]; // 第k个插入的数对应的下标 h[k] = x; down(k); up(k); } int main() { int n; cin >> n; string op; int x, y; while(n--) { cin >> op; if(op == "I") { cin >> x; insert(x); }else if(op == "PM") { cout << h[1] << endl; }else if(op == "D") { cin >> x; del(x); }else if(op == "DM") { Del(); }else { cin >> x >> y; change(x, y); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人