【数据结构】二叉树-堆

目录

一.树概念及性质

二.二叉树的概念与实现

三.堆的概念和结构

四.堆的实现

1.向下调整算法

2. 堆的创建

3.向上调整算法

 4.堆的删除

五.堆排序

六.堆-源码 


一.树概念及性质

树是一种非线性的数据结构,它是由数个节点组成的具有层次关系的集合。之所以叫做树,是因为长得就像一颗倒挂的树。就像下图一样,数据结构中的树是‘根’朝上,‘叶’朝下的。

 树的结构从一个特殊的节点出发:根节点,该节点没有任何的前驱节点,其余节点都是由此根节点延伸而来。

一棵树可以进行拆分为根节点和子树,如上图,第一个根节点下有三个子树,而每个子树又可以继续拆分为根节点和子树,直到某个根节点没有子树为止,从这个角度来看,树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

了解树的基本概念后,接下来就以这幅图来讲解树中的一些常见概念

节点的度:一个节点含有的子树的个数称为该节点的度,例如图中A的度为6

树的度:一棵树中,最大的节点的度,例如图中树的度为6

叶节点:度为0的节点称为叶节点,例如图中的B、C、H、I......

分支节点:度不为0的节点,例如图中:D、E、F、G...... 

父节点:一个节点含有子节点,则该节点为子节点的父节点,例如:D是H的父节点

子节点:与父节点相对,例如:H是D的子节点

二.二叉树的概念与实现

二叉树是树的一种,满足以下定义:

1.二叉树不存在度大于2的节点

2.二叉树的子树有左右之分,次序不能进行颠倒,二叉树是有序树

同样,二叉树也具有特殊形态,那就是完全二叉树和满二叉树。

完全二叉树: 每一层的节点填满以后再进行下一层填入,且每层的填入都是从左到右依次进行,这是一种相当有序的存在,是效率很高的数据结构。

满二叉树:特殊的完全的二叉树,每一层的节点个数都达到最大值。

 二叉树具有的性质(规定根节点的层数为1):

1.一颗非空二叉树的第n层上最多具有2^(n-1)个节点

2.深度为h的二叉树的最大节点个数为2^h-1

3.对于任何一个二叉树,度为0的节点个数(N0)等于度为2节点个数(N2)加1,即:N0=N2+1 

4.对于下标为i的节点,它的父节点下标为(i-1)/2,它的左孩子下标为2i+1,右孩子为2i+2(如果存在的话)

二叉树的存储结构可以分为顺序结构链式结构,但是此处我们只讨论顺序结构,使用顺序结构来实现堆。 如下图所示,使用顺序结构(数组)储存二叉树适合于完全二叉树,否则会造成数组空间的浪费。

 

三.堆的概念和结构

堆满足以下定义:

1.堆总是一颗完全二叉树

2.堆中的某个节点的值总是不大于或不小于其父节点的值

如下图所示:就是典型的大堆和小堆,但需要注意一点:大堆在储存在数组中不一定是降序排列,因为左右孩子节点的大小并没有关系,例如下图中80的左右孩子节点可以交换,同样满足大堆,但就不符合升序了,小堆也是同理。

四.堆的实现

1.向下调整算法

向下调整算法有一个前提:左右子树必须是一个堆。

如下图所示,除了根节点27以外,左右子树都是小堆,那么只需要将27进行向下调整就能使整个二叉树称为堆的结构。如何进行调整?要调整为小堆,27需要和左右孩子中较小的一个进行比较,图中是15,因此27需要先和15进行交换,这样才能满足小堆的结构,但是这还不够,27还要再向下一层进行比较调整,直到找到合适位置或称为叶节点。下图展现了整个过程:

以下是向下调整算法的代码实现,其中a是堆的数组实现,n是数组大小,parent是调整开始的节点,即父节点,根据二叉树性质得到其左孩子为:parent*2+1,但可能还有另外一个右孩子且大小不确定,因此进行判断,后续进行循环判断交换即可。 

void AdjustDown(HPDatatype* a, int n, int parent)
{int child = parent * 2 + 1;//找出孩子节点中大的一个(建大堆)if (child + 1 < n && a[child] > a[child + 1]){child++;}//直到超出数组范围n为止while (child < n){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

 

2. 堆的创建

有了向下调整算法,就下来就能进行建堆了。给定一个数组,不妨将其想象为一个二叉树的结构,要想使整个树成为堆,自然需要调整。如何调整?向下调整需要左右子树为堆的前提,在一个乱序的树中怎么找到堆?这里不妨直接将叶节点看作一个已经建立好的堆,单个的叶节点没有调整的必要,那么从叶节点的父节点开始调整,这样就能建成一个堆,如图中(5,10,11),随后从4开始调整,又有了(4,8,9)这一个堆,有了这两个堆,就又可以从2开始调整,这样(2,4,5,8,9,10,11)都是一个堆了,如此循环往复,最终就能将一个无序的数组调整为一个堆了。

 向下调整建堆非常简单,就是在向下调整算法中加个循环而已,不过需要强调,叶节点不需要进行向下调整,因此起点从最后一个节点的父节点开始即可(上图右体现),那么最后此处k-1是最后一个节点的下标,再-1进行/2就是得到其父节点,随后往后挨个进行调整即可。

//向下调整建堆,k是数组大小
for (int i = (k - 1 - 1) / 2; i < k; i++)
{AdjustDown(a, k, i);
}

3.向上调整算法

如果要向一个堆进行插入数据,此时就需要向上调整算法,此时只需要不断找到父节点进行比较即可,终止条件是找到最顶层的根节点比较后。

 

//向上调整算法
void AdjustUp(HPDatatype* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

 4.堆的删除

删除堆的最后一个节点十分简单,直接删除该数据(让size--)即可。此处讨论的是删除根节点,删除后仍要使剩余数据成为堆。难点在于,如果直接删除根节点,其余节点前移,这样就会破坏原有的关系,原本的孩子节点可能就突然成为跟原本父节点同一层的了,关系全部就打乱了,再恢复就会很麻烦。

那么使用向下调整就会很轻松,交换最后一个节点和根节点,删除最后一个节点,现在的新根节点就是原本的最后一个节点,此时其余的关系仍然不变,即新根节点的左右都是堆的结构,满足向下调整的前提,那么执行向下调整算法一次,就能使成为新的堆。

 

五.堆排序

利用堆来进行排序分为两步:

1.利用向下调整进行建堆,其中排升序就建大堆,排降序就建小堆

2.利用堆删除的思想,将堆顶数据(最大或最小)与最后进行交换,再进行向下调整(除开最后一个已经是最大/最小的数据),这样下一次的堆顶就是第二大/第二小的数据了,反复进行就能排升序/降序了

void HeapSort(int* a, int n)
{//倒序:建小堆//向下调整建堆//从最后一个节点(n-1)的父节点开始for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//n-1是最后一个元素的下标int end = n - 1;while (end > 0){//此时a[0]最小,放最后,再调整又成新的小堆,a[0]成第二小的swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}

六.堆-源码 

头文件:Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int HPDatatype;
typedef struct Heap
{HPDatatype* a;int size;int capacity;
}HP;void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDatatype x);
void HPPop(HP* php);
HPDatatype HPTop(HP* php);
int HPEmpty(HP* php);
int HPSize(HP* php);void swap(HPDatatype* p1, HPDatatype* p2);
void HeapSort(int* a, int n);

源文件:Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"void HPInit(HP* php)
{assert(php);php->a = NULL;php->capacity = php->size = 0;
}void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}void swap(HPDatatype* p1, HPDatatype* p2)
{HPDatatype tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDatatype* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}void HPPush(HP* php, HPDatatype x)
{assert(php);//扩容if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDatatype* tmp = (HPDatatype*)realloc(php->a, sizeof(HPDatatype) * newcapacity);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}//n是元素个数,即最大访问到n-1
void AdjustDown(HPDatatype* a, int n, int parent)
{int child = parent / 2 + 1;while (child < n){//child+1可能造成数组越界访问//只有一个左孩子就不进行++if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}void HPPop(HP* php)
{assert(php);assert(php->size > 0);swap(&php->a[0], &php->a[php->size-1]);php->size--;AdjustDown(php->a, php->size, 0);
}HPDatatype HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}int HPEmpty(HP* php)
{assert(php);return (php->size == 0);
}int HPSize(HP* php)
{assert(php);return php->size;
}void HeapSort(int* a, int n)
{//倒序:建小堆//向上调整建堆:相当于一个个进行插入/*for (int i = 1; i < n; i++){AdjustUp(a, i);}*///向下调整建堆//从最后一个节点(n-1)的父节点开始for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//n-1是最后一个元素的下标int end = n - 1;while (end > 0){//此时a[0]最小,放最后,再调整又成新的小堆,a[0]成第二小的swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}

源文件:Test.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"void testHeap()
{int a[] = { 4,6,7,8,9,1,2,3 };HP hp;HPInit(&hp);for (int i = 0; i < sizeof(a)/sizeof(int); i++){HPPush(&hp, a[i]);}HPPop(&hp);HPPop(&hp);HPDestroy(&hp);
}
void testHeapsort()
{int a[] = { 4,5,6,7,1,2,3,9,8 };HeapSort(a, sizeof(a) / sizeof(a[0]));}int main()
{//testHeap();testHeapsort();return 0;
}

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

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

相关文章

干货 | SDR RFSoC技术框图大放送(附资源)

软件无线电(SDR) 本文参考《Software Defined Radio with Zynq UltraScale RFSoc》&#xff0c;全文共744页。需要的可以给公众号 迪普微科技 发送“SDR”。

【力扣】矩阵中的最长递增路径

一、题目描述 二、解题思路 1、先求出以矩阵中的每个单元格为起点的最长递增路径 题目中说&#xff0c;对于每个单元格&#xff0c;你可以往上&#xff0c;下&#xff0c;左&#xff0c;右四个方向移动。那么以一个单元格为起点的最长递增路径就是&#xff1a;从该单元格往上…

SpringBoot项目启动后访问网页显示“Please sign in“

SpringBoot启动类代码如下 SpringBoot项目启动后访问网页显示"Please sign in"&#xff0c;如图 这是一个安全拦截页面&#xff0c;即SpringSecurity认证授权页面&#xff0c;因为SecurityAutoConfiguration是Spring Boot提供的安全自动配置类&#xff0c;也就是说它…

城规跨考地信:你需要知道的几件事

24考研结束&#xff0c;25地信考研的小伙伴也开始准备。 在这期间发现一个现象&#xff0c;城规跨考GIS的讨论度非常高。 对这一点&#xff0c;我并不感到意外&#xff0c;因为随着地产行业的节节败退&#xff0c;很多单位不需要那么多规划人和建筑人&#xff0c;乃至土木人。…

SpringCloud 微服务中网关如何记录请求响应日志?

在基于SpringCloud开发的微服务中&#xff0c;我们一般会选择在网关层记录请求和响应日志&#xff0c;并将其收集到ELK中用作查询和分析。 今天我们就来看看如何实现此功能。 日志实体类 首先我们在网关中定义一个日志实体&#xff0c;用于组装日志对象 Data public class …

使用Java apache commons包五分钟搞定NCR解析(内附源码)

在网上看到很多关于解析NCR(Numeric Character Reference)字符串的java实现&#xff0c;核心都是通过自定义正则表达式来解析&#xff0c;其实org.apache.commons 已经为我们提供了jar包 解决该问题&#xff0c;非常的方便&#xff01;在这里我就来简单分享一下具体实现方法&am…

这就是英伟达 CEO 黄仁勋所说的人工智能“下一波浪潮”|TodayAI

在台湾一年一度的科技展 COMPUTEX 开幕前的周日&#xff0c;英伟达&#xff08;Nvidia&#xff09;首席执行官黄仁勋&#xff08;Jensen Huang&#xff09;表示&#xff0c;机器人和“理解物理定律的 AI”将成为下一波技术浪潮。他指出&#xff0c;英伟达目前正在推动生成式人工…

MyBatis核心对象

MyBatis核心类对象主要有俩个&#xff1a; 1&#xff1a;对相关配置文件信息进行封装的Configuration对象 2&#xff1a;用来执行数据库操作的Executor对象。 核心对象----存储类对象Configuration Configuration对象主要有三个作用&#xff1a; 1&#xff1a;封装MyBatis…

Pulsar 社区周报 | No.2024-05-30 | BIGO 百页小册《Apache Pulsar 调优指南》

“ 各位热爱 Pulsar 的小伙伴们&#xff0c;Pulsar 社区周报更新啦&#xff01;这里将记录 Pulsar 社区每周的重要更新&#xff0c;每周发布。 ” BIGO 百页小册《Apache Pulsar 调优指南》 Hi&#xff0c;Apache Pulsar 社区的小伙伴们&#xff0c;社区 2024 上半年度的有奖问…

AIGC和ChatGPT有什么区别?

AIGC和ChatGPT有什么区别? 首先先解释一下它们各自的概念 什么是AIGC AIGC&#xff0c;全称为Artificial Intelligence Generated Content&#xff0c;中文译为人工智能生成内容。这是一种利用人工智能技术自动生成内容的生产方式。例如&#xff0c;它可以创作出各种形式的内…

基于PHP+MySQL组合开发的同城便民小程序源码系统 房产出租+求职招聘+相亲交友 带完整的安装代码包以及搭建教程

系统概述 在当今信息化高速发展的时代&#xff0c;同城便民小程序已成为城市居民日常生活中不可或缺的一部分。为了满足广大用户的需求&#xff0c;小编给大家分享一款基于PHPMySQL组合开发的同城便民小程序源码系统。该系统集房产出租、求职招聘、相亲交友等多功能于一体&…

微信小程序使用echarts

思路 五个tab公用一个柱状图组件切换tab以及切换时间改变数据&#xff0c;传入子组件&#xff0c;子组件监听数据重新更新点击柱状图显示具体数值每个时间点有两个柱子&#xff08;高压和低压&#xff09;&#xff0c;柱状图显示高压的最大值到最小值的范围除了血压其余只有一…

Python采集数据处理:利用Pandas进行组排序和筛选

概述 在现代数据处理和分析中&#xff0c;网络爬虫技术变得越来越重要。通过网络爬虫&#xff0c;我们可以自动化地从网页上收集大量的数据。然而&#xff0c;如何高效地处理和筛选这些数据是一个关键问题。本文将介绍如何使用Python的Pandas库对采集到的数据进行组排序和筛选…

基于SpringBoot+Vue研究生志愿填报辅助系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

mp公共字段自动注入

目录 一 什么是公共字段自动注入 二 使用mp实现公共字段自动注入 1.实现步骤 ①导入mp相关依赖 ② 在实体类上给相关字段加上 TableField()注解 ③自定义元数据对象处理器 2.实现原理 一 什么是公共字段自动注入 我们平时在执行更新或者是插入数据功能的时候&#xff0c;…

智绘“水蓝图”,宏电亮相第4届中国(山东)水利科技与生态建设博览会

5月23-25日&#xff0c;第4届中国&#xff08;山东&#xff09;水利科技与生态建设博览会在济南黄河国际会展中心成功召开。展会以“人水和谐&#xff0c;生态山东”为主题&#xff0c;围绕智慧水利建设、水环境治理、水生态保护等领域&#xff0c;展示当下水利行业的新技术、新…

红酒:如何正确地储存红酒

云仓酒庄雷盛红酒&#xff0c;以其优良的品质和丰富的口感&#xff0c;深受广大消费者的喜爱。然而&#xff0c;要想让这些美酒能够长时间保持良好的状态&#xff0c;正确的储存方式是必不可少的。下面&#xff0c;云仓酒庄雷盛红酒将为您详细介绍如何正确地储存红酒。 一、合适…

C语言笔记23 •文件操作•

1.为什么要使用文件&#xff1f; 文件&#xff0c;顾名思义就是存储我们所写在电脑上的文本内容。如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失 了&#xff0c;等再次运⾏程序&#x…

AI工具:如何通过智能助手简化工作流程?

工欲善其事&#xff0c;必先利其器。 随着AI技术与各个行业或细分场景的深度融合&#xff0c;日常工作可使用的AI工具呈现出井喷式发展的趋势&#xff0c;AI工具的类别也从最初的AI文本生成、AI绘画工具&#xff0c;逐渐扩展到AI思维导图工具、AI流程图工具、AI生成PPT工具、AI…

【二叉树】Leetcode 103. 二叉树的锯齿形层序遍历【中等】

二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a; 输入&#xff1a;roo…