二叉树顺序结构——堆的结构与实现

二叉树顺序结构——堆的结构与实现

  • 一、二叉树的顺序结构
  • 二、堆的概念及结构
  • 三、堆的实现
    • 堆向下调整算法
    • 堆的创建
    • 建堆时间复杂度
    • 堆的插入(堆向上调整算法)
    • 堆的删除
    • 堆的代码实现(使用VS2022的C语言)
      • 初始化、销毁
      • 构建、插入、删除
      • 返回堆顶元素、判空、返回有效元素个数
  • 四、完整 Heap.c 源代码

一、二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段
在这里插入图片描述在这里插入图片描述

二、堆的概念及结构

如果有一个关键码的集合K = { k0,k1 ,k2 ,…,k(n - 1) },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: Ki <= K(2 * i +1) 且 Ki <= K(2 * i + 2) (Ki >= K(2 * i + 1) 且 Ki >= K(2 * i + 2) ) ,i = 0,1,2…,则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆
堆的性质:

  1. 堆中某个结点的值总是不大于或不小于其父结点的值;
  2. 总是一棵完全二叉树
    在这里插入图片描述
    在这里插入图片描述

三、堆的实现

注意:

  1. 对于堆中父亲节点下标 i ,它的左孩子总是 i * 2 + 1,右孩子总是 i * 2 + 2;
  2. 对于左右孩子节点 i ,由于整数相除会取整,则它们共同的父亲节点为:(i - 1) / 2;
/*
* leftChild = parent * 2 + 1;
* rightChild = parent * 2 + 2;
* parent = (leftChild - 1) / 2 = (rightChild - 1) / 2;
*/

堆向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整
在这里插入图片描述
在这里插入图片描述

void swap(HPDataType* a, HPDataType* b)
{HPDataType temp = *a;*a = *b;*b = temp;
}// 向下调整代码
void AdjustDown(HPDataType* arr, int n, int parent)
{assert(arr);// 先找到左孩子int child = parent * 2 + 1;while (child < n)						// 当child 超过范围退出{// 假设法if (child + 1 < n && arr[child] > arr[child + 1]){++child;}// 若不符合堆的性质,则调整,反之退出if (arr[parent] > arr[child]){swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}else{break;							// 满足堆的性质,直接退出}}
}

堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 堆的创建
void HeapCreate(pHeap ph, HPDataType* arr, int sz)
{assert(ph);assert(HeapEmpty(ph));		// 堆不为空则不能创建HPDataType* temp = (HPDataType*)realloc(ph->arr, sizeof(HPDataType) * sz);if (temp == NULL){perror("realloc failed");return;}ph->arr = temp;ph->size = sz;ph->capacity = sz;memcpy(ph->arr, arr, sizeof(HPDataType) * sz);// 倒数的第一个非叶子结点的子树下标为: // (总长 - 2) / 2 == 总长 / 2 - 1// 因为对于最后一个叶子节点,它的下标为:// 总长 - 1// 而我们知道非根节点无论是左右孩子,因为整数用除会取整,// 则它的父亲节点均为:// (child - 1) / 2// 即:// lastChild = 总长 - 1;// 倒数的第一个非叶子结点的子树下标 = (lastChild - 1) / 2;// 得 (总长 - 1 - 1) / 2 == 总长 / 2 - 1for (int i = sz / 2 - 1; i >= 0; --i){AdjustDown(ph->arr, sz, i);}
}

建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果):
在这里插入图片描述
因此:建堆的时间复杂度为O(N)

堆的插入(堆向上调整算法)

先插入一个 10 到数组的尾上,再进行向上调整算法,直到满足堆。
在这里插入图片描述

// 向上调整
void AdjustUp(HPDataType* arr, int child)
{assert(arr);int parent = (child - 1) / 2;while (child > 0)				// 当 child 为根节点时退出{if (arr[child] < arr[parent]){swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;					// 当数据满足堆的性质时退出}}
}

堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
在这里插入图片描述

堆的代码实现(使用VS2022的C语言)

堆常用的接口包括:

  1. 初始化、销毁

  2. 构建、插入、删除

  3. 返回堆顶元素、判空、返回有效元素个数

在 Heap.h 中:

#pragma once#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>// 初始化容量
#define INIT_CAPACITY 4// 增容倍率
#define EXPANSION_MULTIPLE 2typedef int HPDataType;typedef struct Heap
{HPDataType* arr;int size;int capacity;
} Heap, * pHeap;// 初始化、销毁
void HeapInit(pHeap ph);void HeapDestroy(pHeap ph);// 构建、插入、删除
void HeapCreate(pHeap ph, HPDataType* arr, int sz);void HeapPush(pHeap ph, HPDataType x);void HeapPop(pHeap ph);// 返回堆顶元素、判空、返回有效元素个数
HPDataType HeapTop(pHeap ph);bool HeapEmpty(pHeap ph);int HeapSize(pHeap ph);

在 Heap.c 中:

初始化、销毁

void HeapInit(pHeap ph)
{assert(ph);ph->arr = NULL;ph->size = 0;ph->capacity = 0;
}void HeapDestroy(pHeap ph)
{assert(ph);free(ph->arr);ph->size = 0;ph->capacity = 0;
}

构建、插入、删除

// 堆的创建
void HeapCreate(pHeap ph, HPDataType* arr, int sz)
{assert(ph);assert(HeapEmpty(ph));		// 堆不为空则不能创建HPDataType* temp = (HPDataType*)realloc(ph->arr, sizeof(HPDataType) * sz);if (temp == NULL){perror("realloc failed");return;}ph->arr = temp;ph->size = sz;ph->capacity = sz;memcpy(ph->arr, arr, sizeof(HPDataType) * sz);// 倒数的第一个非叶子结点的子树下标为: // (总长 - 2) / 2 == 总长 / 2 - 1// 因为对于最后一个叶子节点,它的下标为:// 总长 - 1// 而我们知道非根节点无论是左右孩子,因为整数用除会取整,// 则它的父亲节点均为:// (child - 1) / 2// 即:// lastChild = 总长 - 1;// 倒数的第一个非叶子结点的子树下标 = (lastChild - 1) / 2;// 得 (总长 - 1 - 1) / 2 == 总长 / 2 - 1for (int i = sz / 2 - 1; i >= 0; --i){AdjustDown(ph->arr, sz, i);}
}void HeapPush(pHeap ph, HPDataType x)
{assert(ph);// 先判断空间是否充足if (ph->size == ph->capacity){int newCapacity = ph->capacity == 0 ? INIT_CAPACITY : ph->capacity * EXPANSION_MULTIPLE;HPDataType* temp = (HPDataType*)realloc(ph->arr, sizeof(HPDataType) * newCapacity);if (temp == NULL){perror("realloc failed");return;}ph->arr = temp;ph->capacity = newCapacity;}ph->arr[ph->size++] = x;AdjustUp(ph->arr, ph->size - 1);
}void HeapPop(pHeap ph)
{assert(ph);assert(!HeapEmpty(ph));--ph->size;swap(&ph->arr[0], &ph->arr[ph->size]);AdjustDown(ph->arr, ph->size, 0);
}

返回堆顶元素、判空、返回有效元素个数

HPDataType HeapTop(pHeap ph)
{assert(ph);assert(!HeapEmpty(ph));return ph->arr[0];
}bool HeapEmpty(pHeap ph)
{assert(ph);return ph->size == 0;
}int HeapSize(pHeap ph)
{assert(ph);return ph->size;
}

四、完整 Heap.c 源代码

#include "Heap.h"void HeapInit(pHeap ph)
{assert(ph);ph->arr = NULL;ph->size = 0;ph->capacity = 0;
}void HeapDestroy(pHeap ph)
{assert(ph);free(ph->arr);ph->size = 0;ph->capacity = 0;
}void swap(HPDataType* a, HPDataType* b)
{HPDataType temp = *a;*a = *b;*b = temp;
}// 向下调整代码
void AdjustDown(HPDataType* arr, int n, int parent)
{assert(arr);// 先找到左孩子int child = parent * 2 + 1;while (child < n)						// 当child 超过范围退出{// 假设法if (child + 1 < n && arr[child] > arr[child + 1]){++child;}// 若不符合堆的性质,则调整,反之退出if (arr[parent] > arr[child]){swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}else{break;							// 满足堆的性质,直接退出}}
}// 向上调整
void AdjustUp(HPDataType* arr, int child)
{assert(arr);int parent = (child - 1) / 2;while (child > 0)				// 当 child 为根节点时退出{if (arr[child] < arr[parent]){swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;					// 当数据满足堆的性质时退出}}
}// 堆的创建
void HeapCreate(pHeap ph, HPDataType* arr, int sz)
{assert(ph);assert(HeapEmpty(ph));		// 堆不为空则不能创建HPDataType* temp = (HPDataType*)realloc(ph->arr, sizeof(HPDataType) * sz);if (temp == NULL){perror("realloc failed");return;}ph->arr = temp;ph->size = sz;ph->capacity = sz;memcpy(ph->arr, arr, sizeof(HPDataType) * sz);// 倒数的第一个非叶子结点的子树下标为: // (总长 - 2) / 2 == 总长 / 2 - 1// 因为对于最后一个叶子节点,它的下标为:// 总长 - 1// 而我们知道非根节点无论是左右孩子,因为整数用除会取整,// 则它的父亲节点均为:// (child - 1) / 2// 即:// lastChild = 总长 - 1;// 倒数的第一个非叶子结点的子树下标 = (lastChild - 1) / 2;// 得 (总长 - 1 - 1) / 2 == 总长 / 2 - 1for (int i = sz / 2 - 1; i >= 0; --i){AdjustDown(ph->arr, sz, i);}
}void HeapPush(pHeap ph, HPDataType x)
{assert(ph);// 先判断空间是否充足if (ph->size == ph->capacity){int newCapacity = ph->capacity == 0 ? INIT_CAPACITY : ph->capacity * EXPANSION_MULTIPLE;HPDataType* temp = (HPDataType*)realloc(ph->arr, sizeof(HPDataType) * newCapacity);if (temp == NULL){perror("realloc failed");return;}ph->arr = temp;ph->capacity = newCapacity;}ph->arr[ph->size++] = x;AdjustUp(ph->arr, ph->size - 1);
}void HeapPop(pHeap ph)
{assert(ph);assert(!HeapEmpty(ph));--ph->size;swap(&ph->arr[0], &ph->arr[ph->size]);AdjustDown(ph->arr, ph->size, 0);
}HPDataType HeapTop(pHeap ph)
{assert(ph);assert(!HeapEmpty(ph));return ph->arr[0];
}bool HeapEmpty(pHeap ph)
{assert(ph);return ph->size == 0;
}int HeapSize(pHeap ph)
{assert(ph);return ph->size;
}

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

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

相关文章

20240610 基于QGIS生成地区示意图的地图shp文件

目录 本文目标前置条件具体步骤1. 创建Project2. 插入世界地图3. 对地区示意图进行地理匹配4. 创建shp文件&#xff0c;勾画轨迹 注意事项 本文目标 基于QGIS生成地区示意图的地图shp文件&#xff0c;此shp文件可以用来学习&#xff0c;但是未经审批不可用于发表。 前置条件 …

Python基础教程(十一):数据结构汇总梳理

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

10.3 Go 同步与通信

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

Android Studio Jellyfish版本修改project使用特定jdk版本的步骤

android studio总是把这些东西改来改去让人十分恼火&#xff0c;IDE本身改来改去就让人无法上手就立即工作&#xff0c;很多时间浪费在IDE和gradle的配置和奇奇怪怪现象的斗智斗勇上&#xff0c;搞Android是真的有点浪费生命。一入此坑深不见底 jellyfish版安卓studio已经无法通…

Docker高级篇之轻量化可视化工具Portainer

文章目录 1. 简介2. Portainer安装 1. 简介 Portianer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便管理Docker环境&#xff0c;包括单机环境和集成环境。 2. Portainer安装 官网&#xff1a;https://www.portainer.io 这里我们使用docker命令安装&…

状态管理Vuex

官网&#xff1a;Vuex 是什么&#xff1f; | Vuex (vuejs.org)https://v3.vuex.vuejs.org/zh/ 创建一个vue2的新项目名为vuex-demo&#xff0c;安装命令 npm install vuex3 新建index.js import Vue from vue import Vuex from vuexVue.use(Vuex)const store new Vuex.Store(…

记录自己在xss-labs的通关记录

第十一关&#xff08;referer&#xff09; 直接查看网页源代码&#xff0c;发现四个input被隐藏&#xff0c;不难看出&#xff0c;第四个名为t_ref的<input>标签是http头referer的参数&#xff08;就是由啥地址转跳到这里的&#xff0c;http头的referer会记录有&#xf…

操作系统安全:Windows系统安全配置,Windows安全基线检查加固

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

如何用R语言ggplot2画折线图

文章目录 前言一、数据集二、ggplot2画图1、全部代码2、细节拆分1&#xff09;导包2&#xff09;创建图形对象3&#xff09;主题设置4&#xff09;轴设置5&#xff09;图例设置6&#xff09;颜色7&#xff09;保存图片 前言 一、数据集 数据下载链接见文章顶部 数据&#xff1a…

STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建

STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建 文章目录 STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建功能与作用典型工作流程 1. 硬件原理图介绍2. STM32 CubeMX工程搭建2.1 创建工程2.2 系统配置2.3 USART串口配…

tokenization(一)概述

文章目录 背景基于词&#xff08;Word-based&#xff09;基于字符&#xff08;Character-based&#xff09;子词词元化&#xff08;Subword tokenization&#xff09; 背景 tokenization是包括大语言模型在内所有自然语言处理的任务的基础步骤&#xff0c;其目标是将文本数据转…

【面试干货】聚集索引和非聚集索引区别?

【面试干货】聚集索引和非聚集索引区别? 1、聚集索引&#xff08;Clustered Index&#xff09;1.1 特点1.2 例子 2、非聚集索引&#xff08;Nonclustered Index&#xff09;2.1 特点2.2 例子 3、根本区别 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&…

Sklearn的安装和用法

安装sklearn相对简单&#xff0c;因为它是一个Python库&#xff0c;可以通过Python的包管理器pip来安装。 Windows、macOS和Linux通用步骤&#xff1a; 确保Python已安装&#xff1a; sklearn是基于Python的&#xff0c;所以首先确保你的计算机上安装了Python。推荐使用Pytho…

NLP——电影评论情感分析

python-tensorflow2.0 numpy 1.19.1 tensorflow 2.0.0 导入库 数据加载 数据处理 构建模型 训练 评估 预测 1.基于2层dropout神经网络 2.基于LSTM的网络 #导入需要用到的库 import os import tarfile import urllib. request import tensorflow as tf import numpy a…

5W-35W-150W-300W-500W铝壳功率电阻器

带铝制外壳的电阻器 EAK采用铝型材的导线电阻器将久经考验的导线材料的高脉冲稳定性与优化的导热和高度保护相结合。安装在导热表面上可进一步改善散热并提高稳定性。 连接线有各种长度和材料可供选择。可选配集成温度开关。也可根据客户要求提供定制组件。 该产品有多种版本…

CVE-2023-37474(目录遍历)

靶场简介 Copyparty是一个可移植的文件服务器。在1.8.2版本之前的版本存在一个CTF技巧&#xff0c;该漏洞位于.cpr子文件夹中。路径遍历攻击技术允许攻击者访问位于Web文档根目录之外的文件、目录. 靶场 进入靶场 根据简介访问.cpr目录 使用curl命令访问etc/passwd文件 确定…

kettle_Hbase

kettle_Hbase ☀Hbase学习笔记 读取hdfs文件并将sal大于1000的数据保存到hbase中 前置说明&#xff1a; 1.需要配置HadoopConnect 将集群中的/usr/local/soft/hbase-1.4.6/conf/hbase-site.xml复制至Kettle中的 Kettle\pdi-ce-8.2.0.0-342\data-integration\plugins\pentah…

8.1 基本打印功能

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 在使用“MFC应用”项目模板生成应用程序的过程中&#xff0c;如果在“高级功能”窗口中不取消对打印和打印预览的设置&#xff0c;那么应用程序就已经具备了简单的打印和打…

MySQL—多表查询—练习(2)

一、引言 接着上篇博客《 MySQL多表查询——练习&#xff08;1&#xff09;》继续完成剩下的案例需求。 二、案例 &#xff08;0&#xff09;三张表&#xff08;员工表、部门表、薪资等级表&#xff09; 员工表&#xff1a;emp 部门表&#xff1a;dept 薪资等级表&#xff1a;…

使用 PlatformIO 将文件上传到 ESP32-S3 的 SPIFFS 文件系统

PlatformIO环境 将文件上传到 ESP32-S3 的 SPIFFS 文件系统 介绍&#xff1a; PlatformIO 是一个流行的开发平台&#xff0c;用于编写、构建和上传嵌入式项目。ESP32-S3 是 Espressif 推出的一款功能强大的嵌入式开发板&#xff0c;具有丰富的外设和通信接口。本文将介绍如何…