C++ 堆结构和堆排序(从顶到底/从底到顶的大顶堆)+ 优化
创作时间:
作者:
@小白创作中心
C++ 堆结构和堆排序(从顶到底/从底到顶的大顶堆)+ 优化
引用
CSDN
1.
https://blog.csdn.net/weixin_41987016/article/details/138362120
堆结构和堆排序是C++编程中重要的数据结构和算法之一。本文将详细介绍堆的插入(heapInsert)和向下调整(heapify)操作,并提供两种建立大根堆的方法:从顶到底和从底到顶。同时,本文还将分析堆排序的时间复杂度,并给出完整的代码实现。
一、堆结构和堆排序
(1)heapInsert,向上调整大根堆 和 heapify,向下调整大根堆
// i位置的数,向上调整大根堆
// arr[i] = x,x是新来的!往上看,直到不比父亲大,或者来到0位置(顶)
void heapInsert(vector<int>& arr, int i) {
// i -> 父: (i - 1) / 2
while (arr[i] > arr[(i - 1) / 2]) {
swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
// i位置的数,变小了,又想维持大根堆结构
// 向下调整大根堆
// 当前堆的大小为size
void heapify(vector<int>& arr, int i, int size) {
int l = i * 2 + 1;
while (l < size) {
// 有左孩子,l
// 右孩子,l+1
// 评选,最强的孩子,是哪个下标的孩子
int best = l + 1 < size && arr[l + 1] > arr[l] ? l + 1 : l;
// 上面已经评选了最强的孩子,接下来,当前的数和最强的孩子之前,最强下标是谁
best = arr[best] > arr[i] ? best : i;
// 如果最强的下标,是当前的数,那么当前的数已经满足大根堆结构,退出
if (best == i) { // 最强的是自己
break;
}
swap(arr, best, i);
i = best;
l = i * 2 + 1;
}
}
二、从顶到底建立大根堆
完整代码:
#include <iostream>
using namespace std;
#include <vector>
// 堆结构和堆排序,填函数练习风格
// 测试链接 : https://leetcode.cn/problems/sort-an-array/
class heapSort {
public:
vector<int> sortArray(vector<int> arr) {
if (arr.size() > 1) {
// heapSort1 为从顶到底建堆然后排序
// heapSort2 为从底到顶建堆然后排序
// 用哪个都可以
heapSort1(arr);
//heapSort2(arr);
}
return arr;
}
// i位置的数,向上调整大根堆
// arr[i] = x,x是新来的!往上看,直到不比父亲大,或者来到0位置(顶)
void heapInsert(vector<int>& arr, int i) {
// i -> 父: (i - 1) / 2
while (arr[i] > arr[(i - 1) / 2]) {
swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
// i位置的数,变小了,又想维持大根堆结构
// 向下调整大根堆
// 当前堆的大小为size
void heapify(vector<int>& arr, int i, int size) {
int l = i * 2 + 1;
while (l < size) {
// 有左孩子,l
// 右孩子,l+1
// 评选,最强的孩子,是哪个下标的孩子
int best = l + 1 < size && arr[l + 1] > arr[l] ? l + 1 : l;
// 上面已经评选了最强的孩子,接下来,当前的数和最强的孩子之前,最强下标是谁
best = arr[best] > arr[i] ? best : i;
// 如果最强的下标,是当前的数,那么当前的数已经满足大根堆结构,退出
if (best == i) { // 最强的是自己
break;
}
swap(arr, best, i);
i = best;
l = i * 2 + 1;
}
}
void swap(vector<int>& arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 从顶到底建立大根堆,O(n * logn)
// 依次弹出堆内最大值并排好序,O(n * logn)
// 整体时间复杂度O(n * logn)
void heapSort1(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n; i++) {
heapInsert(arr, i);
}
int size = n;
while (size > 1) {
swap(arr, 0, --size);
heapify(arr, 0, size);
}
}
};
int main() {
//vector<int> arr = { 10,0,20,5,89,70,65,45 };
//vector<int> arr = { 20,30,15,10,9,8,12,45,0,23 };
vector<int> arr = { 5,6,3,1,9,2,4,6 };
heapSort hs;
vector<int> res = hs.sortArray(arr);
for (int i = 0; i < res.size(); i++) {
cout << res[i] << " ";
}
cout << endl;
return 0;
}
三、从底到顶建立大根堆
依次弹出堆内最大值并排好序
完整代码:
#include <iostream>
using namespace std;
#include <vector>
// 堆结构和堆排序,填函数练习风格

// 测试链接 : https://leetcode.cn/problems/sort-an-array/
class heapSort {
public:
vector<int> sortArray(vector<int> arr) {
if (arr.size() > 1) {
// heapSort1 为从顶到底建堆然后排序
// heapSort2 为从底到顶建堆然后排序
// 用哪个都可以
//heapSort1(arr);
heapSort2(arr);
}
return arr;
}
// i位置的数,向上调整大根堆
// arr[i] = x,x是新来的!往上看,直到不比父亲大,或者来到0位置(顶)
void heapInsert(vector<int>& arr, int i) {
// i -> 父: (i - 1) / 2
while (arr[i] > arr[(i - 1) / 2]) {
swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
// i位置的数,变小了,又想维持大根堆结构
// 向下调整大根堆
// 当前堆的大小为size
void heapify(vector<int>& arr, int i, int size) {
int l = i * 2 + 1;
while (l < size) {
// 有左孩子,l
// 右孩子,l+1
// 评选,最强的孩子,是哪个下标的孩子
int best = l + 1 < size && arr[l + 1] > arr[l] ? l + 1 : l;
// 上面已经评选了最强的孩子,接下来,当前的数和最强的孩子之前,最强下标是谁
best = arr[best] > arr[i] ? best : i;
// 如果最强的下标,是当前的数,那么当前的数已经满足大根堆结构,退出
if (best == i) { // 最强的是自己
break;
}
swap(arr, best, i);
i = best;
l = i * 2 + 1;
}
}
void swap(vector<int>& arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 从底到顶建立大根堆,O(n)
// 依次弹出堆内最大值并排好序,O(n * logn)
// 整体时间复杂度O(n * logn)
void heapSort2(vector<int>& arr) {
int n = arr.size();
for (int i = n - 1; i >= 0; i--) {
heapify(arr, i, n);
}
int size = n;
while (size > 1) {
swap(arr, 0, --size);
heapify(arr, 0, size);
}
}
};
int main() {
//vector<int> arr = { 10,0,20,5,89,70,65,45 };
//vector<int> arr = { 20,30,15,10,9,8,12,45,0,23 };
vector<int> arr = { 5,6,3,1,9,2,4,6 };
heapSort hs;
vector<int> res = hs.sortArray(arr);
for (int i = 0; i < res.size(); i++) {
cout << res[i] << " ";
}
cout << endl;
return 0;
}
四、计算复杂度
总结堆结构
- 完全二叉树和数组前缀范围的对应
- i的父亲节点:(i-1)/2,i的左孩子:i2+1,i的左孩子:i2+2
- 堆的定义(大根堆,小根堆),本节课讲解按照大根堆来讲解,小根堆是同理的
- 堆的调整:heapInsert(向上调整),heapify(向下调整)
- heapInsert、heapify方法的单次调用,时间复杂度O(logn),完全二叉树的结构决定的
堆排序
- A.从顶到底建堆,时间复杂度O(nlogn),log1 + log2 + log3 + ... + logn -> O(nlogn)
- B.从底到顶建堆,时间复杂度O(n),总代价就是简单的等比数列关系,为啥会有差异?简单图解一下
- C.建好堆之后的调整阶段,从最大值到最小值依次归位,时间复杂度O(n*logn),不管以什么方式建堆,调整阶段的时间复杂度都是这个 ,所以整体复杂度也是这个额外空间复杂度是O(1),因为堆直接建立在了要排序的数组上,所以没有什么额外空间
注意:堆结构比堆排序有用的多,尤其是和比较器结合之后,后面博客会重点讲述
热门推荐
低开高走的股票投资策略解析与实践技巧
新疆抓饭的正宗做法,别以为只有羊肉抓饭
信用卡欠了钱还不起,银行会用什么方式催收
如何计算贷款的利息成本?这些成本如何影响财务规划?
如何评估职高的教育质量?重点看这四个方面!
高适的一生:从文学家到政治家的传奇经历
出汗多减重快?真相却是……
三国领土变迁:曹操统一北方的关键之举
《哪吒之魔童闹海》中的西海龙王敖闰:形象设计与性格塑造的成功突破
插花:不会挑合适的容器?按照这些要求选花瓶,养花方便又美观
罗翔:人性的本质就是,你越是做事果断、我行我素、不服就干
真三国无双:起源分歧点大揭秘!曹操军竟有三个逆天改命机会,你抓住了吗?
频繁嗜睡?别忽视,这7种疾病可能是幕后黑手!
东野圭吾小说推荐2024:从《白夜行》到《祈念之树》
张红甫揭秘!教你做正宗新疆手抓饭
复读生,去年英语分数39,零基础只背3500词高考英语能考多少分?
七星拱照的寓意
心房扑动的临床表现
日本接近“特大地震”周期点,为何“预警期”只有一周
动作游戏的本质与设计方法论:从机制到美学的深度解析
如何分析午盘拉升的股票?这种分析对投资决策和市场趋势有何指导意义?
亲子教育:兄弟姐妹之争,有时来源于父母内在的匮乏
【命案防控】警示教育典型案例
气血足,不显老,建议多吃这8种,补气养血,气色好显年轻
颁奖典礼上的英语表达,让你自信优雅
Excel中分节功能详解:分页符、工作表和分组功能的使用方法
成为中国·江西新余|这部百科全书,为何经世致用影响数百年?
上证科创板综合指数来了!逾十家基金已上报相关ETF 科创板长期配置价值受关注
五分钟了解《三三制战术》从抗战到现代的步兵作战理念演变
感冒后咳嗽总不好?这6个中成药,止咳祛痰: