【二叉树的顺序结构及实现一-堆】

文章目录

  • 一、二叉树的顺序结构
  • 二、堆的概念及结构
  • 三、堆的实现(以小堆为例)
    • 1、堆的结构体
    • 2、堆的初始化->void HeapInit(HP* hp);
    • 3、堆的销毁->void HeapDestroy(HP* hp);
    • 4、堆的判空->bool HeapEmpty(HP* hp);
    • 5、取堆顶的数据->HPDataType HeapTop(HP* hp);
    • 6、堆的数据个数->int HeapSize(HP* hp);
    • 7、交换函数->void Swap(HPDataType* px, HPDataType* py);
    • 8、堆的打印->void HeapPrint(HP* hp);
    • 9、堆向上调整算法->void AdjustUp(int* a, int child);
    • 10、堆的插入->void HeapPush(HP* hp, HPDataType x);
      • 结合9、10的例子:
      • 运行结果:
      • 代码详解:
    • 11、堆向下调整算法->void AdjustDown(int* a, int n, int parent);
    • 12、堆的删除->void HeapPop(HP* hp);
      • 结合11、12例子:
      • 运行结果:
      • 代码详解:
  • 四、堆的应用
    • 1、 堆排序
    • 2、TOP-K问题


一、二叉树的顺序结构

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

二、堆的概念及结构

如果有一个关键码的集合 k = { k 0 , k 1 , k 2 , . . . , k n − 1 } , k=\left \{k_{0},k_{1},k_{2},...,k_{n-1} \right \}, k={k0,k1,k2,...,kn1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K i ⩽ K 2 ∗ i + 1 且 K i ⩽ K 2 ∗ i + 2 ( K i ⩾ K 2 ∗ i + 1 且 K i ⩾ K 2 ∗ i + 2 ) i = 0 , 1 , 2... , K_{i}\leqslant K_{2*i+1} 且 K_{i}\leqslant K_{2*i+2}\left ( K_{i}\geqslant K_{2*i+1} 且 K_{i}\geqslant K_{2*i+2} \right )i=0,1,2..., KiK2i+1KiK2i+2(KiK2i+1KiK2i+2)i=0,1,2...,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:

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

三、堆的实现(以小堆为例)

1、堆的结构体

typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;

2、堆的初始化->void HeapInit(HP* hp);

void HeapInit(HP* hp)
{assert(hp);hp->a = NULL;hp->size = hp->capacity = 0;
}

3、堆的销毁->void HeapDestroy(HP* hp);

void HeapDestroy(HP* hp)
{assert(hp);free(hp->a);hp->capacity = hp->size = 0;
}

4、堆的判空->bool HeapEmpty(HP* hp);

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

5、取堆顶的数据->HPDataType HeapTop(HP* hp);

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

6、堆的数据个数->int HeapSize(HP* hp);

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

7、交换函数->void Swap(HPDataType* px, HPDataType* py);

void Swap(HPDataType* px, HPDataType* py)
{HPDataType tmp = *px;*px = *py;*py = tmp;
}

8、堆的打印->void HeapPrint(HP* hp);

void HeapPrint(HP* hp)
{for (int i = 0; i < hp->size; ++i){printf("%d ", hp->a[i]);}printf("\n");
}

9、堆向上调整算法->void AdjustUp(int* a, int child);

void AdjustUp(int* a, 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;}}
}

10、堆的插入->void HeapPush(HP* hp, HPDataType x);

void HeapPush(HP* hp, HPDataType x)
{assert(hp);if (hp->size == hp->capacity){size_t newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newCapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->capacity = newCapacity;}hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}

结合9、10的例子:

int main()
{int a[] = { 49,25,34,18,37,19,65,15,27,28 };HP hp;HeapInit(&hp);//将数组的数插入堆中for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){HeapPush(&hp, a[i]);}HeapPrint(&hp);//先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。HeapPush(&hp, 10);HeapPrint(&hp);return 0;
}

运行结果:

在这里插入图片描述

代码详解:

在这里插入图片描述

11、堆向下调整算法->void AdjustDown(int* a, int n, int parent);

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

void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中小的那一个if (child + 1 < n && a[child + 1] < a[child]){++child;}// 如果小的孩子小于父亲,则交换,并继续向下调整if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

12、堆的删除->void HeapPop(HP* hp);

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

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);
}

结合11、12例子:

int main()
{int a[] = { 12,15,19,18,26,34,65,49,25,37,27 };HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){HeapPush(&hp, a[i]);}HeapPrint(&hp);//删除堆顶元素HeapPop(&hp);HeapPrint(&hp);HeapDestroy(&hp);return 0;
}

运行结果:

在这里插入图片描述

代码详解:

在这里插入图片描述

四、堆的应用

1、 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1)、建堆:
升序:建大堆
降序:建小堆
2). 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
3)、例子:

void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中小的那一个if (child + 1 < n && 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 HeapSort(int* a, int n)
{//下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。//O(N)建小堆,降序for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}//依次选数,调堆//O(N*logN)for (int end = n - 1; end > 0; --end){Swap(&a[end], &a[0]);//再调堆,选出次小的数AdjustDown(a, end, 0);}
}int main()
{int a[] = { 70, 56, 30, 25, 15, 10, 75, 33, 50, 69 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");HeapSort(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");return 0;
}

4)、运行结果
在这里插入图片描述
5)、详解例子建堆和排序
在这里插入图片描述
6)、建堆时间复杂度
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的
就是近似值,多几个节点不影响最终结果):

在这里插入图片描述
因此:建堆的时间复杂度为O(N)。

2、TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆

  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
3、例子:

// 在N个数找出最大的前K个  or  在N个数找出最小的前K个
void PrintTopK(int* a, int n, int k)
{HP hp;HeapInit(&hp);// 创建一个K个数的小堆for (int i = 0; i < k; ++i){HeapPush(&hp, a[i]);}// 剩下的N-K个数跟堆顶的数据比较,比他大,就替换他进堆for (int i = k; i < n; ++i){if (a[i] > HeapTop(&hp)){HeapPop(&hp);HeapPush(&hp, a[i]);//hp.a[0] = a[i];//AdjustDown(hp.a, hp.size, 0);}}HeapPrint(&hp);HeapDestroy(&hp);
}void TestTopk()
{int n = 1000000;int* a = (int*)malloc(sizeof(int) * n);srand(time(0));for (size_t i = 0; i < n; ++i){a[i] = rand() % 1000000;}// 再去设置10个比100w大的数a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[5355] = 1000000 + 3;a[51] = 1000000 + 4;a[15] = 1000000 + 5;a[2335] = 1000000 + 6;a[9999] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}int main()
{TestTopk();return 0;
}

4、运行结果:
在这里插入图片描述


以上是本篇文章的全部内容,如果文章有错误或者有看不懂的地方,多和喵博主交流。互相学习互相进步。如果这篇文章对你有帮助,可以给喵博主一个关注,你们的支持是我最大的动力。

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

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

相关文章

抖店新手该如何运营?

我是电商珠珠 在抖店开好之后&#xff0c;大部分新手都不知道怎么去运营&#xff0c;今天&#xff0c;我就来给大家详细的讲一下。 第一步&#xff0c;店铺基础设置 我一直跟我的学生讲&#xff0c;一定要懂基本流程&#xff0c;只有前期将流程跑通了后期才可以毫无压力。 …

Java基础综合练习(飞机票,打印素数,验证码,复制数组,评委打分,数字加密,数字解密,抽奖,双色球)

练习一&#xff1a;飞机票 需求: ​ 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 ​ 按照如下规则计算机票价格&#xff1a;旺季&#xff08;5-10月&#xff09;头等舱9折&#xff0c;经济舱8.5折&#xff0c;淡季&#xff08;11月到来…

【algorithm】自动驾驶常见常考的几个模型和推导,顺便总结自己遇到的考题经验不断更新之———控制版

写在前面 本来快达成目标了&#xff0c;没想到公司遭受了问题&#xff0c;公司和同事我感觉还是挺好的&#xff0c;有国企的正规也有小企业的灵活&#xff0c;大家都很有学习欲望。 作为本次再次复习回忆如下&#xff1a; 把之前面试准备的 机器学习&#xff08;基本搬运到CSD…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

电脑突然不能使用win+x后的快捷键的解决方法

在一次使用电脑后我习惯性的winxuh进行休眠&#xff0c;但是失败了&#xff0c;我发现winx后并没有出现曾经常用的快捷键方式。 左边图片显示的是正常情况。我遇到的情况是图片右边快捷键位没有了&#xff0c;并且也不能进行快捷操作。 国内的网站我都搜索过了&#xff0c;甚至…

outlook邮箱群发邮件方法?邮箱如何群发?

outlook邮箱群发邮件如何使用&#xff1f;QQ邮箱设置群发的步骤&#xff1f; Outlook邮箱群发邮件&#xff1a;必要性 Outlook邮箱作为全球广泛使用的邮件服务之一&#xff0c;不仅提供了便捷的邮件收发功能&#xff0c;还支持多种附件、日历提醒及强大的联系人管理。Outlook…

Python 实现给 pdf 文件自动识别标题并增添大纲

一、背景&#xff1a; 客户方提供过来一个开放平台的pdf文档&#xff0c;文档里有几十个接口&#xff0c;没有大纲和目录可以定位到具体内容&#xff0c;了解整体的API功能&#xff0c;观看体验极度差劲&#xff0c;所以想使用Python代码自动解析pdf文档&#xff0c;给文档增添…

某人寿保险公司基础架构云化与小机数仓下移实践

随着数据中心 IT 基础架构的不断演进&#xff0c;云计算、大数据、移动互联的需求日益高涨&#xff0c;快速敏捷、易于维护以及扩展性&#xff0c;逐渐成为金融机构在升级数据中心时重点考虑的方面。 某人寿保险公司&#xff08;以下简称“客户”&#xff09;过往采用传统三层架…

PS插件一键生成超治愈向日葵花海

金黄色的向日葵总能给人带来治愈的感觉&#xff0c;仿佛在这里能够疗愈心灵所有的伤口。今天我们通过START AI来生成一片美丽的向日葵花海~ 这是小编使用的关键词&#xff0c;负面词需要填写你不想要拥有的&#xff0c;能够让生成的结果更贴合你的想法 最后的生成效果就如下图…

IC工程师级别与薪资是怎样的?资深工程师一文带你了解清楚

入行IC之后&#xff0c;想必大家更关心的就是工程师薪资和级别&#xff0c;因为入行的大多数也是工程师。 国际的一流企业基本上工程师分为以下几个级别&#xff1a;普通工程师&#xff0c;资深工程师&#xff0c;主管工程师&#xff0c;资深主管&#xff0c;总工, 资深总工&am…

电子电器架构(E/E)演化 —— 车载以太网

电子电器架构&#xff08;E/E&#xff09;演化 —— 车载以太网 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 本文13000字。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一…

笔记1:基于锚框(先验框)的目标检测

一、边缘框&#xff08;bounding box&#xff09; 1.1 定义 边缘框&#xff1a;真实标注的物体位置 2.1 表示方式 1、&#xff08;x1,y1)和(x2,y2) 2、&#xff08;x1,y1)和w,h 二、锚框(anchor box)/先验框&#xff08;prior bounding box&#xff09; 2.1 定义 对边缘…

Django 学习教程- Django模板(Template)

系列 Django 学习教程-介绍与安装-CSDN博客 Django 学习教程- Hello world入门案例-CSDN博客 前言 在上一章节中我们使用django.http.HttpResponse() 来输出 "Hello World&#xff01;"。该方式将数据与视图混合在一起&#xff0c;不符合 Django 的 MTV 思想。 本…

Linux network — 网络层收发包流程及 Netfilter 框架浅析

Linux network — 网络层收发包流程及 Netfilter 框架浅析 1. 前言2. 基础网络知识2.1 网络分层模型2.2 数据包协议分层2.3 sk_buff 结构2.4 收发包整体框架 3. 网络层&#xff08;IPv4&#xff09;收发包流程4. Netfilter 框架4.1 IPv4 网络层的 Netfilter Hook 点4.2 iptable…

算法——队列+宽搜(BFS)

队列这种数据结构大都服务于一个算法——宽搜&#xff08;BFS&#xff09;。宽搜还可以运用到二叉树、图、迷宫最短路径问题、拓扑排序等等 N叉数的层序遍历 N叉树的层序遍历 题目解析 给定一个 N 叉树&#xff0c;返回其节点值的_层序遍历_。&#xff08;即从左到右&#…

使用Wireshark进行网络流量分析

目录 Wireshark是什么&#xff1f; 数据包筛选 筛选指定ip 使用逻辑运算符筛选 HTTP模式过滤 端口筛选 协议筛选 包长度筛选 数据包搜索 数据流分析 数据包导出 Wireshark是什么&#xff1f; 通过Wireshark&#xff0c;我们可以捕获和分析网络数据包&#xff0c;查看…

【Maven】工程依赖下载失败错误解决

在使用 Maven 构建项目时&#xff0c;可能会发生依赖项下载错误的情况&#xff0c;主要原因有以下几种&#xff1a; 下载依赖时出现网络故障或仓库服务器宕机等原因&#xff0c;导致无法连接至 Maven 仓库&#xff0c;从而无法下载依赖。 依赖项的版本号或配置文件中的版本号错…

SPI

一、简介 SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在 时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接 口可以是3线式或4线式。 MOSI(Master Output Slave Input) – 主设备输出/从设备输入信号&#xff1b;MISO(Master Input Slave Output) – 主…

经典卷积神经网络-ResNet

经典卷积神经网络-ResNet 一、背景介绍 残差神经网络(ResNet)是由微软研究院的何恺明、张祥雨、任少卿、孙剑等人提出的。ResNet 在2015 年的ILSVRC&#xff08;ImageNet Large Scale Visual Recognition Challenge&#xff09;中取得了冠军。残差神经网络的主要贡献是发现了…

Nginx 反向代理负载均衡

Nginx 反向代理负载均衡 普通的负载均衡软件&#xff0c;如 LVS&#xff0c;其实现的功能只是对请求数据包的转发、传递&#xff0c;从负载均衡下的节点服务器来看&#xff0c;接收到的请求还是来自访问负载均衡器的客户端的真实用户&#xff1b;而反向代理就不一样了&#xf…