性质

  • 二叉堆是一颗完全二叉树,而完全二叉树是把元素排列成树的形状。
  • 堆中某个节点的值总不大于其父节点的值最大堆(相应的可以定于最小堆)

// 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
constexpr int parent(const int index) const {
if (index == 0) {
throw new NoParent();
}
return (index - 1) / 2;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
constexpr int leftChild(const int index) const {
return (index * 2) + 1;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
constexpr int rightChild(const int index) const {
return (index * 2) + 2;
}

可以先阅读底层动态数组Array

添加



首先我们堆中的数据使用数组排列的,所以添加一个元素就是在层序遍历的最右端,也就是最下面一层的最后添加一个元素。但是以数组来看就是在索引为10的地方添加一个元素。

void add(const T &e) {
data->addLast(e); //在数组的末尾添加元素
shiftUp(data->getSize() - 1); //上浮添加元素的索引
}
  • 时间复杂度O(logn)

但是添加的元素不符最大堆的性质,索引我需要一些调整,而这个调整就是一个上浮的过程。

void shiftUp(int index) {
//如果传入索引小于等于0并且父元素大于等于子元素则停止循环
while (index > 0 && data->get(index) > data->get(parent(index))) {
data->swap(index, parent(index)); //位置交换
index = parent(index); //把父节点的索引给子节的
}
}

取出最大元素



最大堆的最大元素就是其根节点元素,取出的操作只能取出这个元素,对于数组来说,根结点就是索引为0的元素。



我们把堆中最后一个元素顶到堆顶去,然后再把最后一个元素删除。然而这样就又不符合最大堆的性质。



这样的话,其不大于它的子节点,此时又要进行调整,这个调整的过程叫做下沉。在这个过程中每次需要下沉的时候都要和它的两个孩子进行比较,选择其中较大的进行交换位置。

  • 时间复杂度O(logn)
//返回最大的元素
T findMax() const {
if (data->isEmpty()) {
throw Empty();
}
return data->get(0);
}
//取出最大的元素
T extractMax() {
T ret = findMax();
data->swap(0, data->getSize() - 1);
data->removeLast();
shiftDown(0);
return ret;
}
//下沉
void shiftDown(int k) {
while (leftChild(k) < data->getSize()) {
int j = leftChild(k);
//j保存的是左右孩子中较大的元素索引
if (j + 1 < data->getSize() && data->get(j + 1) > data->get(j)) {
j = rightChild(k);
}
//如果子节点小于等于父节点了,就结束
if (data->get(k) > data->get(j)) {
break;
}
data->swap(k, j);
k = j;
}
}

取出堆中最大的元素,并替换成元素e

  • 时间复杂度O(logn)
T replace(T e) {
T ret = findMax();
data->set(0, e);
shiftDown(0);
return ret;
}

Heapify

将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn),Heapify的过程,算法复杂度是O(n)。

MaxHeap(T arr[], const int n) {
data = new Array<T>(arr, n);
for (int i = parent(n - 1); i >= 0; --i) {
shiftDown(i);
}
}

对比使用与不适用Heapify代码

#include <iostream>
#include "MaxHeap.h"
#include <cassert> template<typename T>
double testHeap(T testData[], int n, bool isHeapify) {
clock_t startTime = clock();
MaxHeap<T> *maxHeap;
if (isHeapify) {
maxHeap = new MaxHeap<T>(testData, n);
} else {
maxHeap = new MaxHeap<T>();
for (int i = 0; i < n; ++i) {
maxHeap->add(testData[i]);
}
} T *arr = new T[n];
for (int j = 0; j < n; ++j) {
arr[j] = maxHeap->extractMax();
} for (int k = 1; k < n; ++k) {
assert(arr[k - 1] >= arr[k]);
}
std::cout << "Test MaxHeap completed." << std::endl;
clock_t endTime = clock();
return double(endTime - startTime) / CLOCKS_PER_SEC;
} int main() {
int n = 5000000;
int *testData = new int[n];
for (int i = 0; i < n; ++i) {
testData[i] = rand() % INT32_MAX;
}
double time1 = testHeap(testData, n, false);
std::cout << "Without heapify :" << time1 << " s " << std::endl;
double time2 = testHeap(testData, n, true);
std::cout << "With heapify :" << time2 << " s " << std::endl;
return 0;
}

代码清单

//
// Created by cheng on 2021/7/10.
// #ifndef MAXHEAP_MAXHEAP_H
#define MAXHEAP_MAXHEAP_H #include "Array.h" template<typename T>
class MaxHeap {
public: class NoParent {
}; class Empty {
}; MaxHeap() {
data = new Array<T>();
} ~MaxHeap() {
delete data;
data = nullptr;
} MaxHeap(const int capacity) {
data = new Array<T>(capacity);
} MaxHeap(T arr[], const int n) {
data = new Array<T>(arr, n);
for (int i = parent(n - 1); i >= 0; --i) {
shiftDown(i);
}
} constexpr int getSize() const {
return data->getSize();
} constexpr bool isEmpty() const {
return data->isEmpty();
} // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
constexpr int parent(const int index) const {
if (index == 0) {
throw new NoParent();
}
return (index - 1) / 2;
} void add(const T &e) {
data->addLast(e);
shiftUp(data->getSize() - 1);
}
//返回最大元素
T findMax() const {
if (data->isEmpty()) {
throw Empty();
}
return data->get(0);
}
//取出最大的元素
T extractMax() {
T ret = findMax();
data->swap(0, data->getSize() - 1);
data->removeLast();
shiftDown(0);
return ret;
}
//取出堆中最大的元素,并替换成元素e
T replace(T e) {
T ret = findMax();
data->set(0, e);
shiftDown(0);
return ret;
} void print() {
data->print();
} private: void shiftDown(int k) {
while (leftChild(k) < data->getSize()) {
int j = leftChild(k);
if (j + 1 < data->getSize() && data->get(j + 1) > data->get(j)) {
j = rightChild(k);
}
if (data->get(k) > data->get(j)) {
break;
}
data->swap(k, j);
k = j;
}
} void shiftUp(int index) {
while (index > 0 && data->get(index) > data->get(parent(index))) {
data->swap(index, parent(index));
index = parent(index);
}
} // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
constexpr int leftChild(const int index) const {
return (index * 2) + 1;
} // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
constexpr int rightChild(const int index) const {
return (index * 2) + 2;
} private:
Array<T> *data;
}; #endif //MAXHEAP_MAXHEAP_H

最大堆(MaxHeap)的更多相关文章

  1. java——最大堆 MaxHeap

    使用数组来实现最大堆 堆是平衡二叉树 import Date_pacage.Array; public class MaxHeap<E extends Comparable <E>& ...

  2. 数据结构-详解优先队列的二叉堆(最大堆)原理、实现和应用-C和Python

    一.堆的基础 1.1 优先队列和堆 优先队列(Priority Queue):特殊的"队列",取出元素顺序是按元素优先权(关键字)大小,而非元素进入队列的先后顺序. 若采用数组或链 ...

  3. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  4. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  5. 面试题目——《CC150》高等难题

    面试题18.1:编写一个函数,将两个数字相加.不得使用+或其他算数运算符. package cc150.high; public class Add { public static void main ...

  6. 二叉堆(二)之 C++的实现

    概要 上一章介绍了堆和二叉堆的基本概念,并通过C语言实现了二叉堆.本章是二叉堆的C++实现. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的C++实现(完整源码)4. 二叉堆的C++测试程 ...

  7. 二叉堆(三)之 Java的实现

    概要 前面分别通过C和C++实现了二叉堆,本章给出二叉堆的Java版本.还是那句话,它们的原理一样,择其一了解即可. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的Java实现(完整源码) ...

  8. 3.Python3标准库--数据结构

    (一)enum:枚举类型 import enum ''' enum模块定义了一个提供迭代和比较功能的枚举类型.可以用这个为值创建明确定义的符号,而不是使用字面量整数或字符串 ''' 1.创建枚举 im ...

  9. pat04-树9. Path in a Heap (25)

    04-树9. Path in a Heap (25) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue Insert ...

  10. 萌新笔记之堆(heap)

    前言(萌新感想): 以前用STL的queue啊stack啊priority_queue啊,一直很想懂原理,现在终于课上到了priority_queue,还有就是下周期中考,哈哈,所以写几篇blog总结 ...

随机推荐

  1. RCE代码执行漏和命令执行漏洞

    前置知识: 漏洞检测: 在了解漏洞概念前,应该先知道一下这个漏洞如何检测的,我们应该或多或少听过白盒测试(白盒),黑盒测试(黑盒). 白盒测试: 白盒测试是对源代码和内部结构的测试,测试人员是可以知道 ...

  2. Linux后台进程启停脚本模板

    目录 启动脚本 停止脚本 在Linux上启动程序后台运行时,往往需要输入一堆复杂的命令,为了能快速编写一个完善的启动脚本,整理一个通用的启停脚本模板如下. 脚本支持从任意位置执行,不存在路径问题,启动 ...

  3. MySQL的随机排序(random orderby)

    MySQL的随机排序(random orderby)是指在查询数据库时,将结果集以随机的方式排列.这种排序方式可以用于有趣的应用场景,例如实现随机音乐播放.广告推荐等. 要实现MySQL的随机排序,可 ...

  4. dataclass装饰器

    简介 根据定义dataclass时指"一个带有默认值的可变的namedtuple" 简单来说,就是你定义一个很普通的类,@dataclass装饰器可以 帮你生成__repr__._ ...

  5. The OCaml Language Cheatsheets

    The OCaml Language Cheatsheets OCaml v.4.08.1 Syntax Implementations are in .ml files, interfaces ar ...

  6. 【Azure 存储服务】关于中国区Azure Storage Account 存储账号服务误删除后的恢复问题

    问题描述 在Azure上,如果需要恢复之前删除的存储账户(Storage Account), 有什么办法呢? 问题解答 Azure 现在推出了自主恢复已删除的存储账号的功能,具体步骤如下: 第一步: ...

  7. 【Azure 云服务】如果云服务证书过期会有什么影响,证书时间应该如何查看

    问题描述 如果云服务证书过期会有什么影响,证书时间应该如何查看 问题答案 在云服务中,有两种证书:服务证书 和 管理证书 什么是服务证书? 通过浏览器访问云服务中的服务(Web Role)时候所使用的 ...

  8. Java 线程通信的应用:经典例题:生产者/消费者问题

    1 package bytezero.threadcommunication; 2 3 /** 4 * 线程通信的应用:经典例题:生产者/消费者问题 5 * 6 * 7 * 8 * @author B ...

  9. Hello 2024C. Grouping Increases(贪心)

    我们只需要记录每个数结尾的数是多少(有点最长上升子序列的味道) 这种子序列的题目很多都是这样的,因为不需要连续很多时候我们只记录最后一个元素是多少. \(记s为较大子序列结尾当前的数,t为较小子序列结 ...

  10. Mysql使用limit深度分页优化

    1.背景: mysql使用select * limit offset, rows分页在深度分页的情况下.性能急剧下降. 例如:select * 的情况下直接⽤limit 600000,10 扫描的是约 ...