当前位置: 首页 > news >正文

【数据结构入门】二叉树之堆的实现

文章目录

  • 前言
  • 一、树
    • 1.1 树的概念
    • 1.2 树的相关概念
  • 二、二叉树
    • 2.1 二叉树的概念
    • 2.2 特殊的二叉树
    • 2.3 二叉树的性质
  • 三、堆
    • 3.1 堆的概念
    • 3.2 堆的性质
    • 3.3 堆的存储
    • 3.4 堆的实现
      • 3.4.1 堆的初始化
      • 3.4.2 堆的销毁
      • 3.4.1 堆向上调整算法
      • 3.4.2 堆向下调整算法
      • 3.4.3 堆的创建
      • 3.4.4 堆的插入
      • 3.4.5 堆的删除
      • 3.4.6 堆顶元素
      • 3.4.7 判断堆是否为空
      • 3.4.8 堆的元素个数
      • 3.4.9 Heap.h
  • 总结

前言

堆是一种重要的数据结构,常用于解决各种问题,如优先队列、排序算法、图算法等。堆具有很多特性,其中最常见的是最大堆和最小堆。最大堆中,每个节点的值都大于等于其子节点的值,而最小堆则相反,每个节点的值都小于等于其子节点的值。
 在本文中,我们将详细介绍堆的概念、性质和操作。我们将以一个具体的例子来说明堆的构建和调整过程,并通过图示展示堆的结构。最后,我们还将讨论堆在实际应用中的一些常见用途和算法。通过学习堆,您将能够更好地理解和应用这一重要的数据结构。
 要想理解堆,我们先需要知道树的概念,事实上堆是一种二叉树。

一、树

1.1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的

  • 有一个特殊的结点,称为根结点,根结点没有前驱结点
  • 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。
    在这里插入图片描述
    注意:树形结构中,子树之间不能有交集,否则就不是树形结构
    在这里插入图片描述

1.2 树的相关概念

在这里插入图片描述

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6

叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I…等结点为叶结点

非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G…等结点为分支结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点(也就是亲兄弟结点)

树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

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

二、二叉树

2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成
    在这里插入图片描述
    从上图可以看出:

1.二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种情况复合而成的:
在这里插入图片描述

2.2 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2 k − 1 2^k -1 2k1 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
    在这里插入图片描述

2.3 二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2 ( i − 1 ) 2^{(i-1)} 2(i1) 个结点.
  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2 h − 1 2^h-1 2h1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n 0 n_0 n0, 度为2的分支结点个数为 n 2 n_2 n2,则有 n 0 n_0 n0 n 2 n_2 n2+1
  4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度h= l o g 2 ( n + 1 ) log_2(n+1) log2(n+1). (ps: l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)是log以2为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
    1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
    2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
    3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

三、堆

3.1 堆的概念

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

3.2 堆的性质

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

3.3 堆的存储

堆一般使用顺序结构的数组来存储,但是普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。堆作为一个完全二叉树是完全可以用数组来存储的,不会浪费太多空间。

3.4 堆的实现

3.4.1 堆的初始化

  1. assert 判空,防止野指针的出现
  2. 初始容量设为4
void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}

3.4.2 堆的销毁

void HeapDestory(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = 0;php->capacity = 0;
}

数组的销毁,比较简单

3.4.1 堆向上调整算法

基本思想:

向上调整算法有一个前提:左右子树必须是一个堆,才能调整。所以在建堆时,要建好大堆或者小堆。

具体步骤:

  1. 将新插入的元素放置在堆的最后一个位置(通常是数组的末尾)。
  2. 将该元素与其父节点进行比较。
  3. 若该元素大于(或小于,具体根据堆是最大堆还是最小堆而定)其父节点的值,则交换该元素和其父节点的位置。
  4. 继续向上对比和交换,直到满足堆的性质或达到堆的根节点。
void ADjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//不建议while(parent>=0) 因为parent到不了-1,但是也可以跑,因为后面if条件不满足while (child>0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

3.4.2 堆向下调整算法

向下调整算法与向上调整一样,但是它的时间复杂度比向上调整算法低:左右子树必须是一个堆,才能调整。所以在建堆时,要建好大堆或者小堆。

具体步骤:

  1. 从根结点开始向下调整。
  2. 将该元素与较小或较大的子节点进行比较。
  3. 若该元素大于(或小于,具体根据堆是最大堆还是最小堆而定)其子节点的值,则交换该元素和子节点的位置。
  4. 继续向下对比和交换,直到满足堆的性质或达到堆的叶节点。
void ADjustDown(HPDataType* a, int sz, int parent)
{int child = parent * 2 + 1;while (child < sz){//选出左右孩子中大的一个//这里child+1的判断在前,不要先访问再判断//这里a[child + 1] > a[child] 建大堆用>, 建小堆用<if (child + 1 < sz && a[child + 1] < a[child]){//这地方可能会越界++child;}//这里a[child] > a[parent] 建大堆用>, 建小堆用<if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

3.4.3 堆的创建

向下调整建堆

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void HeapInitArray(HP* php, int* a, int n)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = n;//建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){ADjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);ADjustDown(a, end, 0);--end;}
}

3.4.4 堆的插入

判断容量不够时需要扩容,空间够,则插入后需要向上调整

void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;ADjustUp(php->a, php->size - 1);
}

3.4.5 堆的删除

堆的删除要从堆顶开始删除,删除时先将堆顶元素与堆底元素进行交换,然后去掉堆底元素(也就是删掉了堆顶元素),然后再进行向下调整,保证堆的结构依旧满足。
这里从堆顶开始删除才有意义,堆顶的数据依次会是最大值(大堆)或者最小值(小堆)。

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

3.4.6 堆顶元素

HPDataType HeapTop(HP* php)
{assert(php);return php->a[0];
}

3.4.7 判断堆是否为空

bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

3.4.8 堆的元素个数

int HeapSize(HP* php)
{assert(php);return php->size;
}

3.4.9 Heap.h

#pragma oncetypedef int HPDataType;#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);
void HeapDestory(HP* php);
void HeapInitArray(HP* php, int* a, int n);void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void Swap(HPDataType* p1, HPDataType* p2);void ADjustUp(HPDataType* a, int child);
void ADjustDown(HPDataType* a, int sz, int parent);

总结

堆的主要优点之一是能够在O(log n)的时间复杂度内找到最值,例如最大堆可以在常数时间内找到最大值。这使得堆可以在动态数据集合中高效地插入和删除元素。另外,堆还能够在排序算法中扮演重要角色,例如堆排序可以在O(n log n)的时间复杂度内对数据进行排序。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 百日筑基第五十七天-虚拟线程
  • 前端框架(三件套)
  • git cherry-pick命令使用分享
  • Android UI:PopupWindow:API
  • 《机器学习》 逻辑回归 大批量数据的下采样 <8>
  • git本地仓库同步到远程仓库
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
  • EasyExcel_通过模板导出(多sheet、列表、图片)
  • Linux ps命令详解
  • 【NI国产替代】NI‑9235四分之一桥应变计,8通道C系列应变/桥输入模块
  • 基于LSTM的交通流量预测算法及Python实现
  • ECMAScript性能优化技巧与陷阱(上)
  • 上门搬家小程序源码开发:打造便捷高效的搬家新体验
  • Triplet Loss解析及示例计算
  • 私域流量与公域流量的主要区别
  • ----------
  • [iOS]Core Data浅析一 -- 启用Core Data
  • Android系统模拟器绘制实现概述
  • canvas 高仿 Apple Watch 表盘
  • Docker容器管理
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Promise面试题,控制异步流程
  • SpiderData 2019年2月25日 DApp数据排行榜
  • vue数据传递--我有特殊的实现技巧
  • 聊聊flink的TableFactory
  • 判断客户端类型,Android,iOS,PC
  • -- 数据结构 顺序表 --Java
  • 微信小程序--------语音识别(前端自己也能玩)
  • 应用生命周期终极 DevOps 工具包
  • 在Unity中实现一个简单的消息管理器
  • 智能合约Solidity教程-事件和日志(一)
  • 【云吞铺子】性能抖动剖析(二)
  • #控制台大学课堂点名问题_课堂随机点名
  • (13)Hive调优——动态分区导致的小文件问题
  • (35)远程识别(又称无人机识别)(二)
  • (39)STM32——FLASH闪存
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (十一)c52学习之旅-动态数码管
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • ./configure、make、make install 命令
  • .NET Core 中的路径问题
  • .NET 分布式技术比较
  • .Net 基于.Net8开发的一个Asp.Net Core Webapi小型易用框架
  • .NET 漏洞分析 | 某ERP系统存在SQL注入
  • .NET 设计模式初探
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • .NET简谈设计模式之(单件模式)
  • .net项目IIS、VS 附加进程调试
  • .Net语言中的StringBuilder:入门到精通