热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法

想快速入门数据结构,推荐订阅作者的初阶数据结构专栏!此专栏预计更新:顺序表,链表,栈,队列,二叉树,排序算法等等初阶数据结构我们通过c语言实现,所以此专栏也可以帮助大家巩固大家的c

想快速入门数据结构,推荐订阅作者的初阶数据结构专栏!此专栏预计更新:顺序表,链表,栈,队列,二叉树,排序算法等等

初阶数据结构我们通过c语言实现,所以此专栏也可以帮助大家巩固大家的c语言知识

源代码已上传至我的码云

前言

二叉树算是初阶数据结构的一个新坑吧,不仅仅是因为难度比前面的数据结构提升了一个档次,而且这也是我们学的第一种非线性结构

我们在前面学的数据结构,无论是顺序表还是链表,不管它们在物理中的存储方式如何,它们的逻辑一定是串在一起的。

但是树形结构却不一样,它是一种有层次的结构,其元素具有一对多的特性

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》
所以,相对于以前几期,它是一种全新的数据结构

目录

  • 树的概念
    • 树的定义
    • 树的相关名词
  • 二叉树的概念
    • 一些特殊的二叉树
  • 二叉树的顺序存储结构——堆
  • 堆的实现
    • 插入和向上调整算法
  • 堆的删除与向下调整算法
树的概念

树的定义

要了解二叉树,首先我们要对普通的树有一定的认知

在前言提到过,树是一种非线性数据结构,数据的组织具有层次性

它的定义:始终由根结点(没有前驱结点的结点)和子树构成

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》
所以我们可以知道:树是递归定义的

树的相关名词

节点的度:一个节点含有的子树的个数称为该节点的度;

叶节点或终端节点:度为0的节点称为叶节点;

非终端节点或分支节点:度不为0的节点;

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的度:一棵树中,最大的节点的度称为树的度;

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次;

堂兄弟节点:双亲在同一层的节点互为堂兄弟;

节点的祖先:从根到该节点所经分支上的所有节点;

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。

森林:由m(m>0)棵互不相交的树的集合

二叉树的概念

定义:二叉树就是度不会大于2的树(即分支数不会大于2的树)

一些特殊的二叉树

满二叉树:每一层的结点达到最大值

完全二叉树:前n-1层是一颗满二叉树,最后一棵树的结点由左到右连续存放

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

二叉树的顺序存储结构——堆

现在来到了本章的重点,堆

堆是一种在物理上连续存储,但在逻辑上却是一颗二叉树的数据结构

怎么把逻辑结构与物理结构联系起来呢?

这里有几个公式:

通过父亲结点找到孩子结点,设数组开始下标为0:

leftchild=parent2+1
leftchild=parent
2+1
这里的child和parent是它们对应的数组下标

通过孩子结点找到父亲结点

parent=(child-1)/2

注意:由于数组元素是连续存放的,所以为了保证数组的空间利用率,堆对应的逻辑结构应该是一棵完全二叉树,(即中间没有空白结点空间)

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

但是,不是一个普通的数组就是堆的,它需要满足一个特性

根结点的值始终比它的孩子结点的值小(大)

前者叫小堆,后者叫大堆

比如上图,就是一个典型的小堆

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

堆的实现

由于初始化和销毁等其它函数与线性表的定义一样,因为堆的逻辑结构是个数组,所以这篇文章中不再阐述

重点讲插入和删除

插入和删除我们要遵循一个原则:那就是堆的性质永远不会改变

为了遵循以上原则,我们需要引入向上和向下调整算法

这里我们以小堆为例来创建堆

插入和向上调整算法

我们以这个小堆来举例
《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

首先,插入操作前面与顺序表差不多,需要检查容量

if (hp->size == hp->capacity)
{
int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//这里是防止堆为空
HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
if (!tmp)
{
printf("malloc fail\n");
exit(-1);
}
hp->a = tmp;
hp->capacity = newcapacity;
}

插入我们可以直接先在数组的后面插入

hp->a[hp->size] = x;
hp->size++;

比如我们要插入一个数据9,然后逻辑结构就变成了这样

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》
这样,9比34小了,显然不满足小端的特征,所以我们需要向上调整算法,把它调整到满足堆的特征的所在位置

向上调整,只会影响这个结点的祖先结点

思路:与它的父亲结点不断比较,若不满足要求则交换,满足要求就停止算法

图示:

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

代码:

void AdjustUp(HPDataType* a, int size, int child)
{
assert(a);
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 AdjustUp(HPDataType* a, int size, int child)
{
assert(a);
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 HeapPush(HP* hp, HPDataType x)
{
assert(hp);
if (hp->size == hp->capacity)
{
int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
if (!tmp)
{
printf("malloc fail\n");
exit(-1);
}
hp->a = tmp;
hp->capacity = newcapacity;
}
hp->a[hp->size] = x;
hp->size++;
AdjustUp(hp->a, hp->size, hp->size - 1);
}
堆的删除与向下调整算法

注意:这里的删除是直接删除堆顶元素

我们最开始的思路是,能不能像顺序表的头删一样,直接挪动数据来删除呢?

显然是不行的,因为这样会把堆的结构完全打乱,大家可以脑补一下,这里就不图示了

所以我们需要这么删除

先把第一个元素与最后一个元素交换,再进行顺序表尾删,这样保证了前面堆的结构不会被打乱

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》

void HeapPop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
}

但我们的77被换上去了,不满足堆的特性了,所以为了满足堆的特性,使用向下调整算法

思路:

先选出左右孩子中较小的孩子(先默认为左孩子,再与右孩子进行比较,若默认结果不成立,就更新孩子结点)

与向上调整算法异曲同工

《初阶数据结构——初识二叉树及其应用——堆——及其向下向上调整算法》
代码实现

void AdjustDown(HPDataType* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;//默认与左孩子交换
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
child++;//不满足默认条件就换为右孩子
}
if (a[child] < a[parent])//交换
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}

完成删除代码

void AdjustDown(HPDataType* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown(hp->a, hp->size, 0);
}

推荐阅读
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • C语言判断正整数能否被整除的程序
    本文介绍了使用C语言编写的判断正整数能否被整除的程序,包括输入一个三位正整数,判断是否能被3整除且至少包含数字3的方法。同时还介绍了使用qsort函数进行快速排序的算法。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • Learning to Paint with Model-based Deep Reinforcement Learning
    本文介绍了一种基于模型的深度强化学习方法,通过结合神经渲染器,教机器像人类画家一样进行绘画。该方法能够生成笔画的坐标点、半径、透明度、颜色值等,以生成类似于给定目标图像的绘画。文章还讨论了该方法面临的挑战,包括绘制纹理丰富的图像等。通过对比实验的结果,作者证明了基于模型的深度强化学习方法相对于基于模型的DDPG和模型无关的DDPG方法的优势。该研究对于深度强化学习在绘画领域的应用具有重要意义。 ... [详细]
  • C语言常量与变量的深入理解及其影响
    本文深入讲解了C语言中常量与变量的概念及其深入实质,强调了对常量和变量的理解对于学习指针等后续内容的重要性。详细介绍了常量的分类和特点,以及变量的定义和分类。同时指出了常量和变量在程序中的作用及其对内存空间的影响,类似于const关键字的只读属性。此外,还提及了常量和变量在实际应用中可能出现的问题,如段错误和野指针。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 数据结构与算法的重要性及基本概念、存储结构和算法分析
    数据结构与算法在编程领域中的重要性不可忽视,无论从事何种岗位,都需要掌握数据结构和算法。本文介绍了数据结构与算法的基本概念、存储结构和算法分析。其中包括线性结构、树结构、图结构、栈、队列、串、查找、排序等内容。此外,还介绍了图论算法、贪婪算法、分治算法、动态规划、随机化算法和回溯算法等高级数据结构和算法。掌握这些知识对于提高编程能力、解决问题具有重要意义。 ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
author-avatar
夡敧
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有