数据结构-----堆(完全二叉树)

 目录

前言

一.堆

1.堆的概念

2.堆的存储方式

二.堆的操作方法

1.堆的结构体表示

2.数字交换接口函数

3.向上调整(难点)

4.向下调整(难点)

5.创建堆

 6.堆的插入

 7.判断空

8.堆的删除

 9.获取堆的根(顶)元素

10.堆的遍历

 11.销毁堆

完整代码

三.堆的应用(堆排序)

1.算法介绍

2.基本思想

3.代码实现

4.算法分析


前言

         今天我们开始学习一种二叉树,没错,那就是完全二叉树,完全二叉树又叫做堆,在此之前我们简单介绍过了完全二叉树的概念(链接:数据结构-----树和二叉树的定义与性质_灰勒塔德的博客-CSDN博客),这种类型的二叉树又有什么特点呢?代码怎么去实现呢?应用有那些呢?下面就一起来看看吧!

一.堆

1.堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,物理层面上是一个数组,逻辑上是一个完全二叉树。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

  • 堆总是一棵完全二叉树。

  • 满足任意父节点都大于子节点的称作为大堆

  • 满足任意子节点都大于父节点的称作为小堆

  • tip:(下文会以大堆的创建为示例)

如图所示:

 

2.堆的存储方式

堆的储存原则是从上到下,从左到右,也就是说先有上面的父节点才会有子节点,先有左子节点,才会有右子节点 ,所以堆可以去通过一个数组完整的表示出来,如下图所示:

二.堆的操作方法

以下是一个堆要实现的基本功能,下面我会一一去详细解释说明

void swap(DataType* a, DataType* b);//交换数据void Adjust_Up(DataType* data, int child, int n);//向上调整void Adjust_Down(DataType* data, int parent, int n);//向下调整void Heap_Create(Heap* hp, DataType* data, int n);//创建堆bool isEmpty(Heap* hp);//判断空void Heap_Insert(Heap* hp, DataType x);//堆的插入void Heap_Del(Heap* hp);//堆的删除操作DataType Heap_Root(Heap* hp);//获取根元素void Heap_show(Heap* hp);//堆的遍历void Heap_Destory(Heap* hp);//堆的销毁

1.堆的结构体表示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define Maxsize 50//顺序结构
//堆(完全二叉树)
typedef int DataType;	//定义数据的类型
typedef struct Heap
{int size;	//当前节点数量int capacity;	//最大容量DataType* data;	//数据储存地址
}Heap;

2.数字交换接口函数

//数据交换接口
void swap(DataType* a, DataType* b) {DataType temp = *a;*a = *b;*b = temp;
}

3.向上调整(难点)

        创建大堆时,向上调整的目的是,在有子节点位置的情况下,进行与父节点的大小比较,如果子节点大于父节点,那么就进行交换,然后新的子节点就是上一个的父节点,依次这样比较下去,最后到根节点为止,如图所示:

 代码实现:

//向上调整
void Adjust_Up(DataType* data, int child, int n) {int parent = (child - 1) / 2;while (child > 0) {//如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到//新的父节点,继续进行同样的操作,直到根节点为止if (data[child] > data[parent]){swap(&data[child], &data[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

4.向下调整(难点)

        同样的还有向下调整,如果有了当前的父节点位置,那么就要跟子节点进行比较,但是子节点有左和右子节点,所以左右子节点也要去比较,取到其中比较大的子节点与父节点比较,如果这个字节点大于父节点的话,那就进行数字交换,然后新的父节点就是上一个的子节点,依次往下遍历进行同样的操作。

代码实现: 

//向下调整
void Adjust_Down(DataType* data, int parent, int n) {int child = parent * 2 + 1;while (child <n ) {if (child+1 < n && data[child] < data[child+1]){//如果右子节点大于左子节点,那就child+1,选中到右子节点child++;}if (data[child] > data[parent]) {//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作swap(&data[child], &data[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

5.创建堆

已有一个数组{ 5,1,2,3,6,4,8 },怎么把这个数组放入堆里面呢?同样的,空间申请去申请到一块连续的空间,然后依次把数据存入到这个数组里面去,最后进行向下调整,以达到堆的形式。

放入堆之后如下图所示: 

代码实现:

//创建堆
void Heap_Create(Heap* hp, DataType* data, int n) {assert(hp);hp->data = (DataType*)malloc(sizeof(DataType) * n);if (!hp->data) {printf("ERROR\n");exit(-1);}for (int i = 0; i < n; i++) {hp->data[i] = data[i];//赋值}hp->size = n;hp->capacity = Maxsize;for (int j = (n - 1) / 2; j >= 0; j--) {//创建完成了之后,就要进行向下调整Adjust_Down(hp->data, j ,hp->size);}
}

 6.堆的插入

堆的插入,就是在堆的最后面去添加一个元素,添加完成之后,就要去进行向上调整操作,如下图所示:

代码实现: 

//堆的插入
void Heap_Insert(Heap* hp, DataType x) {assert(hp);//如果此时的堆空间满了,那么就要去扩容空间if (hp->size == hp->capacity) {DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType)  * (hp->capacity+1));//追加1个空间if (!temp) {printf("ERROR\n");exit(-1);}hp->data = temp;hp->data[hp->size] = x;hp->size++;hp->capacity++;}else{hp->data[hp->size] = x;hp->size++;}Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
}

 7.判断空

//判断空
bool isEmpty(Heap* hp) {assert(hp);return hp->size == 0;
}

8.堆的删除

堆的删除操作是删除掉根节点,过程是,先把最后一个节点与根节点进行交换,然后重新进行向下调整。(堆的删除操作,删除掉的是根节点!

代码实现: 

//堆的删除,删除根节点
void Heap_Del(Heap* hp) {assert(hp);if (!isEmpty(hp)) {swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换hp->size--;Adjust_Down(hp->data, 0, hp->size);//向下调整}
}

 9.获取堆的根(顶)元素

//获取堆顶元素
DataType Heap_Root(Heap* hp) {assert(hp);if (!isEmpty(hp))return hp->data[0];elseexit(0);
}

10.堆的遍历

堆的遍历就直接按照数组的顺序去遍历就行了,完全二叉树的逻辑上是从上到下,从左到右去遍历的,代码如下:

//输出堆元素(按照顺序)
void Heap_show(Heap* hp) {assert(hp);if (isEmpty(hp)) {printf("The Heap is etmpy\n");return;}for (int i = 0; i < hp->size; i++)printf("%d ", hp->data[i]);
}

 11.销毁堆

//堆的销毁
void Heap_Destory(Heap* hp) {assert(hp);hp->size = hp->capacity = 0;free(hp);//释放空间
}

完整代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define Maxsize 50//顺序结构
//堆(完全二叉树)
typedef int DataType;	//定义数据的类型
typedef struct Heap
{int size;	//当前节点数量int capacity;	//最大容量DataType* data;	//数据储存地址
}Heap;//数据交换接口
void swap(DataType* a, DataType* b) {DataType temp = *a;*a = *b;*b = temp;
}//向上调整
void Adjust_Up(DataType* data, int child, int n) {int parent = (child - 1) / 2;while (child > 0) {//如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到//新的父节点,继续进行同样的操作,直到根节点为止if (data[child] > data[parent]){swap(&data[child], &data[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//向下调整
void Adjust_Down(DataType* data, int parent, int n) {int child = parent * 2 + 1;while (child <n ) {if (child+1 < n && data[child] < data[child+1]){//如果右子节点大于左子节点,那就child+1,选中到右子节点child++;}if (data[child] > data[parent]) {//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作swap(&data[child], &data[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//创建堆
void Heap_Create(Heap* hp, DataType* data, int n) {assert(hp);hp->data = (DataType*)malloc(sizeof(DataType) * n);if (!hp->data) {printf("ERROR\n");exit(-1);}for (int i = 0; i < n; i++) {hp->data[i] = data[i];//赋值}hp->size = n;hp->capacity = Maxsize;for (int j = (n - 1) / 2; j >= 0; j--) {//创建完成了之后,就要进行向下调整Adjust_Down(hp->data, j ,hp->size);}
}//判断空
bool isEmpty(Heap* hp) {assert(hp);return hp->size == 0;
}//堆的插入
void Heap_Insert(Heap* hp, DataType x) {assert(hp);//如果此时的堆空间满了,那么就要去扩容空间if (hp->size == hp->capacity) {DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType)  * (hp->capacity+1));//追加1个空间if (!temp) {printf("ERROR\n");exit(-1);}hp->data = temp;hp->data[hp->size] = x;hp->size++;hp->capacity++;}else{hp->data[hp->size] = x;hp->size++;}Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
}//堆的删除,取出根节点
void Heap_Del(Heap* hp) {assert(hp);if (!isEmpty(hp)) {swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换hp->size--;Adjust_Down(hp->data, 0, hp->size);//向下调整}
}//获取堆顶元素
DataType Heap_Root(Heap* hp) {assert(hp);if (!isEmpty(hp))return hp->data[0];elseexit(0);
}//输出堆元素(按照顺序)
void Heap_show(Heap* hp) {assert(hp);if (isEmpty(hp)) {printf("The Heap is etmpy\n");return;}for (int i = 0; i < hp->size; i++)printf("%d ", hp->data[i]);
}//堆的销毁
void Heap_Destory(Heap* hp) {assert(hp);hp->size = hp->capacity = 0;free(hp);//释放空间
}

三.堆的应用(堆排序)

1.算法介绍

        堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

2.基本思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

① 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
② 依次将根节点与待排序序列的最后一个元素交换
③ 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

3.代码实现

#include<stdio.h>
#include<assert.h>
//数据交换接口
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}//向下调整
void Adjust_Down(int* data, int parent, int n) {int child = parent * 2 + 1;while (child < n) {if (child + 1 < n && data[child] < data[child + 1]){//如果右子节点大于左子节点,那就child+1,选中到右子节点child++;}if (data[child] > data[parent]) {//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作swap(&data[child], &data[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//堆排序算法
void Heap_sort(int* arr, int n) {assert(arr);for (int i = (n - 2) / 2; i >= 0; i--) {Adjust_Down(arr, i, n);}//先形成最大堆int end = n - 1;//从小到大排序while (end > 0) {swap(&arr[0], &arr[end]);Adjust_Down(arr, 0, end);end--;	//此时最后一个也就是当前的最大值已经排序好了}
}int main() {int a[9] = { 5,1,2,3,6,4,8,2,10 };Heap_sort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); i++) {printf("%d ", a[i]);}
}
//输出
//1 2 2 3 4 5 6 8 10

4.算法分析

  • 平均时间复杂度:O(nlogn)
  • 最佳时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 稳定性:不稳定

 以上就是本期的内容,我们下次见!

 分享一张壁纸:

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

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

相关文章

提升群辉AudioStation音乐体验,实现公网音乐播放

文章目录 本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是本教程使用环境&#xff1a;1 群晖系统安装audiostation套件2 下载移动端app3 内网穿透&#xff0c;映射至公网 很多老铁想在上班路上听点喜欢的歌或者相声解解闷儿&#xff0c;于是打开手…

什么是HTTP头部(HTTP headers)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 理解 HTTP 头部&#xff08;HTTP Headers&#xff09;⭐ HTTP 头部的分类⭐ HTTP 头部的应用⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#x…

C++ 学习系列 -- std::vector (未完待续)

一 std::vector 是什么&#xff1f; vector 是c 中一种序列式容器&#xff0c;与前面说的 array 类似&#xff0c;其内存分配是连续的&#xff0c;但是与 array 不同的地方在于&#xff0c;vector 在运行时是可以动态扩容的&#xff0c;此外 vector 提供了许多方便的操作&…

1小时掌握Python操作Mysql数据库之pymysql模块技术

大家好&#xff0c;我是python222小锋老师。前段时间卷了一套 Python3零基础7天入门实战 近日锋哥又卷了一波课程&#xff0c;Python操作Mysql数据库的pymysql技术&#xff0c;文字版视频版。1小时掌握。 视频版教程 1小时掌握Python操作Mysql数据库之pymysql模块技术 文字版…

Rust vs C++ 深度比较

Rust由于其强大的安全性受到大量关注&#xff0c;被认为C在系统编程领域最强大的挑战者。本文从语言、框架等方面比较了两者的优缺点。原文: Rust vs C: An in-depth language comparison Rust和C的比较是开发人员最近的热门话题&#xff0c;两者之间有许多相似之处&#xff0c…

使用FastChat部署Baichuan2

1. 引言 近来&#xff0c;大型语言模型的市场需求呈现出蓬勃发展的态势。然而&#xff0c;仅仅掌握模型的数据准备和训练是不够的&#xff0c;模型的部署方法也变得至关重要。在这篇文章中&#xff0c;我们将以Baichuan2为例&#xff0c;利用FastChat进行模型部署的实战操作。…

两种常见矩形框旋转方法推导及其C++实现

在已知矩形中心点、长宽和旋转角度&#xff08;定义为矩形最长边与X轴正方向的夹角&#xff09;&#xff0c;如何确定矩形四个顶点的坐标&#xff0c;通常有以下两种处理方法。 法一&#xff1a;直接对顶点进行旋转 比如下图虚线框矩形是实线框矩形绕矩形中心点旋转后得到。在…

深度学习实战基础案例——卷积神经网络(CNN)基于Xception的猫狗识别|第2例

文章目录 一、环境准备二、数据预处理三、构建模型四、实例化模型五、训练模型5.1 构建训练函数5.2 构建测试函数5.3 开始正式训练 六、可视化精度和损失七、个体预测总结 今天使用轻量级的一个网络Xception做一个简单的猫狗识别案例&#xff0c;我的环境具体如下&#xff1a; …

记一次STM32F4 HAL IAP开发过程踩坑

第一次在HAL库上做IAP&#xff0c;不太熟悉库结构&#xff0c;被坑了一早上… MCU上做了一个shell&#xff0c;实现了goto命令跳转到APP区执行&#xff08;只是为了开发时方便&#xff09;。跳转到APP前和以前一样清理了所有初始化过的外设&#xff0c;也对中断进行了处理&…

MySQL数据库的索引和事务

目录 一、索引 1.1Mysql索引 1.2索引的作用 1.3 创建索引的依据 1.4 普通索引 修改表方式创建索引 删除索引 1.5 唯一索引 修改表方式创建 删除索引 1.6 主键索引 修改表方式创建 1.7 组合索引 1.8 全文索引 1.9查看索引 二、事务 2.1事务概念 2.2事务的ACID特…

rocketmq-spring-boot-starter 2.1.0 事务消息移除参数txProducerGroup

statrer引入 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.3</version></dependency> starter 2.0.2对应rocketmq 4.4.0 starter 2.1.0对应rocke…

NSDT 3D孪生场景搭建:阵列摆放详解

阵列摆放概念 阵列摆放是指将物体、设备或元件按照一定的规则和间距排列组合的方式。在工程和科学领域中&#xff0c;阵列式摆放常常用于优化空间利用、提高效率或增强性能。 阵列摆放通常需要考虑间距、角度、方向、对称性等因素&#xff0c;以满足特定的要求和设计目标。不同…

Seata流程源码梳理下篇-TC

我们上篇简单梳理了下TM、RM的一些流程&#xff08;离现在过得挺久的了&#xff0c;这篇我们这篇来梳理下TC的内容。 TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态&#xff0c;驱动全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器 定…

C++ Primer 第5章 语句

C Primer 第5章 语句 5.1 简单语句一、空语句二、别漏写分号&#xff0c;也别多写分号三、复合语句&#xff08;块&#xff09; 5.2 语句作用域5.3 条件语句5.3.1 if语句一、使用if else语句二、嵌套if语句三、注意使用花括号四、悬垂else五、使用花括号控制执行路径 5.3.2 swi…

Oracle分区的使用详解:创建、修改和删除分区,处理分区已满或不存在的插入数据,以及分区历史数据与近期数据的操作指南

一、前言 什么是表分区: Oracle的分区是一种将表或索引数据分割为更小、更易管理的部分的技术。它可以提高查询性能、简化维护操作,并提供更好的数据组织和管理。 表分区和表空间的区别和联系: 在Oracle数据库中,表空间(Tablespace)是用于存储表、索引和其他数据库对…

Baichuan2 技术报告笔记

文章目录 预训练预训练数据模型架构TokenizerPositional EmbeddingsAcitivations and NormalizationsOptimizations 对齐Supervised Fine-TuningRLHF 安全性预训练阶段对齐阶段 参考资料 对Baichuan2技术报告阅读后的笔记 Baichuan2 与其他大模型的对比如下表 预训练 预训练数…

【Java开发】Redis位图实现统计日活周活月活

最近研究了使用 Redis 的位图功能统计日活周活等数据&#xff0c;特来和大家分享下&#xff0c;Redis 位图还可用于记录用户签到情况、判断某个元素是否存在于集合中等。 1 Redis 位图介绍 Redis 位图是一种特殊的数据结构&#xff0c;它由一系列位组成&#xff0c;每个位只能…

洛谷P8815:逻辑表达式 ← CSP-J 2022 复赛第3题

【题目来源】https://www.luogu.com.cn/problem/P8815https://www.acwing.com/problem/content/4733/【题目描述】 逻辑表达式是计算机科学中的重要概念和工具&#xff0c;包含逻辑值、逻辑运算、逻辑运算优先级等内容。 在一个逻辑表达式中&#xff0c;元素的值只有两种可能&a…

Appilot发布:打造面向DevOps场景的开源AI助手

今日&#xff0c;数澈软件Seal &#xff08;以下简称“Seal”&#xff09;宣布推出面向 DevOps 场景的 AI 助手 Appilot&#xff0c;这款产品将充分利用 AI 大语言模型的能力为用户提供变革性的部署和应用管理体验。Seal 此次发布的 Appilot 项目&#xff0c;可以让用户直接输入…

leetcode 22. 括号生成

2023.9.24 看到组合两个字&#xff0c;想到了回溯。 大致思路是将所有可能的组合列出来&#xff0c;通过中止条件筛选掉无效的括号。 第一个中止条件&#xff1a;如果右括号数量大于左括号&#xff0c;那括号肯定无效。 第二个中止条件&#xff1a;当左右括号数量相等&#x…