二叉树 - 堆 | 数据结构中的小技巧大作用

news2024/12/28 23:06:38

在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路
🌅 有航道的人,再渺小也不会迷途。


在这里插入图片描述

文章目录

    • 一、堆的概念及介绍
    • 二、结构图示
    • 三、堆的代码实现(图解)
      • 3.1 创建堆结构体即接口
      • 3.2 堆的初始化 && 交换两个数(用于parent 和 child 的交换 )
      • 3.3 堆的向上调整
      • 3.4 堆向下调整算法(以小堆为例)
      • 3.5 堆的创建
        • 【向上调整建堆时间复杂度】
        • 【向下调整建堆时间复杂度】
      • 3.6 堆的插入
      • 3.7 堆的删除
      • 3.8 取堆顶的数据
      • 3.9 求堆的数据个数
      • 3.10 堆的判空
    • 四、源代码
      • 4.1 Heap.h文件
      • 4.2 Heap.c文件
      • 4.3 Test.c文件

一、堆的概念及介绍

堆(Heap)是计算机科学中一类特殊的数据结构的统称。
堆通常是一个可以被看做一棵完全二叉树的数组。

需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

堆满足下列性质:

  • 堆中某个节点的值总是不大于或不小于父节点的值。
  • 总是一棵完全二叉树

将根节点最大的堆叫做最大堆大根堆,根节点最小的堆叫做最小堆小根堆
在这里插入图片描述
在这里插入图片描述


二、结构图示

二叉堆是一颗完全二叉树,且堆中某个节点的值总是不大于其父节点的值,该完全二叉树的深度为 k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边。

其中堆的根节点最大称为最大堆,如下图所示:
在这里插入图片描述
我们可以使用数组存储二叉堆,右边的标号是数组的索引。
在这里插入图片描述

在这里插入图片描述

假设当前元素的索引位置为 i,可以得到规律:

parent(i) = i/2(取整)
left child(i) = 2*i+1
right child(i) = 2*i +2

三、堆的代码实现(图解)

3.1 创建堆结构体即接口

typedef int HPDataType; //数据元素类型
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap; //堆的结构

//堆的初始化 (可要可不要)
void HeapInit(Heap* hp);
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

3.2 堆的初始化 && 交换两个数(用于parent 和 child 的交换 )

// 堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);

	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

3.3 堆的向上调整

注意:此算法的前提是 在进行向上调整前此树已经是堆
向上调整操作用于在插入新元素时保持堆的性质。

算法思想如下:

  1. 首先,计算给定节点的父节点索引。如果当前节点是根节点(即索引为0),则没有父节点,不需要进行向上调整。

  2. 然后,进入一个循环,条件是当前节点的索引大于0。这是因为根节点已经是堆中的最大值(对于大顶堆)或最小值(对于小顶堆),无需再向上调整。

  3. 在循环中,比较当前节点和其父节点的值。如果当前节点的值小于其父节点的值(对于大顶堆)或大于其父节点的值(对于小顶堆),则需要进行向上调整。

  4. 交换当前节点和其父节点的值,将父节点移动到正确的位置。然后更新当前节点的索引为父节点的索引,并重新计算父节点的索引。

  5. 如果当前节点的值大于或等于其父节点的值(对于大顶堆)或小于或等于其父节点的值(对于小顶堆),则说明已经到达了正确的位置,可以跳出循环。

通过以上步骤,可以实现向上调整操作,确保堆的性质得到维护。
在这里插入图片描述

//向上调整 --- 插入时使用,保证堆的结构
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while(parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent]) //< 改成 > 就是大堆的向上调整
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

时间复杂度分析
最坏的情况下是从第一个非叶子节点一路比较到根节点,比较的次数为完全二叉树的高度-1,即时间复杂度为 O(log2N)

3.4 堆向下调整算法(以小堆为例)

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。
向下调整算法有一个前提:左右子树必须是一个堆,才能调整

int array[] = {27,15,19,18,28,34,65,49,25,37};

在这里插入图片描述

算法思想如下:

  1. 假设当前节点的左孩子为最小值节点。
  2. 判断当前节点是否有右孩子,如果有且右孩子的值小于左孩子的值,则将右孩子的下标赋值给child
  3. 如果当前节点的值大于child节点的值,说明需要向下调整,交换当前节点和child节点的值。
  4. 更新parentchild,继续向下调整。
  5. 如果当前节点的值小于等于child节点的值,说明已经找到合适的位置,跳出循环。

通过以上步骤,可以实现向下调整操作,确保堆的性质得到维护。
在这里插入图片描述

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//假设左孩子小
	int child = parent * 2 + 1;
	while (child < size)
	{
		//如果右孩子更小,则将child的下标置为右孩子的下标
		if (child + 1 < size && a[child] > a[child + 1]) //后面的 “>” 改成 “<”即为大堆的向下调整
		{
			child++;
		}

		if (a[child] < a[parent]) // “<”改成“>”即为大堆的向下调整
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

时间复杂度分析
最坏的情况即图示的情况,从根一路比较到叶子节点,比较的次数为完全二叉树的高度,即时间复杂度为 O(log2N)

3.5 堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆

int a[] = {1,5,3,8,7,6}; 

在这里插入图片描述
【1】向上调整建堆

//向上调整建对堆 --- 0(N*logN)
int n = sizeof(a) / sizeof(a[0]);

for (int i = 1; i < n; i++)
{
	AdjustUp(a, i);
}

【2】向下调整建堆

// 向下要调整建堆 --- O(N) 
int n = sizeof(a) / sizeof(a[0]);
//找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,遇到一个节点,应用向下调整
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	AdjustDown(a, n, i);
}

【3】模拟堆插入的过程建堆

// 堆的构建 --- 小堆 O(logN)
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	//模拟堆插入的过程建堆
	assert(hp);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	hp->size = 0;
	hp->capacity = n;
	for (int i = 0; i < n; i++)
	{
		HeapPush(hp, a[i]);
	}
}
【向上调整建堆时间复杂度】

在这里插入图片描述

【向下调整建堆时间复杂度】

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

在这里插入图片描述

3.6 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
在这里插入图片描述

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->size * 2;
		HPDataType* temp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = temp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size++] = x;

	AdjustUp(hp->a, hp->size - 1);
}

3.7 堆的删除

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

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);

	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	//从父亲的位置开始往下调
	AdjustDown(hp->a, hp->size, 0);
}

3.8 取堆顶的数据

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);

	return hp->a[0];
}

3.9 求堆的数据个数

// 求堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);

	return hp->size;
}

3.10 堆的判空

// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;
}

四、源代码

4.1 Heap.h文件

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int HPDataType; //数据元素类型
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap; //堆的结构

//堆的初始化(可要可不要)
void HeapInit(Heap* hp);
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

4.2 Heap.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"


// 堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);

	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

// 堆的构建 --- 小堆
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	hp->size = 0;
	hp->capacity = n;
	for (int i = 0; i < n; i++)
	{
		HeapPush(hp, a[i]);
	}
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);

	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}


//向上调整 --- 插入时使用,保证堆的结构
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//while(parent >= 0)
	while (child > 0)
	{
		if (a[child] < a[parent]) //< 改成 > 就是大堆的向上调整
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// 堆的插入 --- O(logN)
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->size * 2;
		HPDataType* temp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = temp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size++] = x;

	AdjustUp(hp->a, hp->size - 1);
}

//向下调整 --- 删除的时候使用
void AdjustDown(HPDataType* a, int size, int parent)
{
	//假设左孩子小
	int child = parent * 2 + 1;
	while (child < size)
	{
		//如果右孩子更小,则将child的下标置为右孩子的下标
		if (child + 1 < size && a[child] > a[child + 1]) //后面的 “>” 改成 “<”
		{
			child++;
		}

		if (a[child] < a[parent]) // “<”改成“>”即为大堆的向下调整
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
	
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);

	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	//从父亲的位置开始往下调
	AdjustDown(hp->a, hp->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);

	return hp->a[0];
}
// 求堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);

	return hp->size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;
}

4.3 Test.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

void Test1()
{
	int a[] = { 4,6,2,1,5,8,2,9 };
	Heap hp;
	int len = sizeof(a) / sizeof(a[0]);
	//HeapInit(&hp);
	模拟堆插入的过程建堆
	//for (int i = 0; i < len; i++)
	//{
	//	HeapPush(&hp, a[i]);
	//}
	HeapCreate(&hp, a, len);

	//打印堆中前k个元素
	/*int k = 4;
	while (k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}*/
	//打印堆
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp)); 
		HeapPop(&hp);
	}
	printf("\n");
}

int main()
{
	Test1();
	return 0;
}

今天的分享到此结束,后续将继续向大家带来更多数据结构的小知识!

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

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

相关文章

【RT-DETR有效改进】华为 | GhostnetV2移动端的特征提取网络效果完爆MobileNet系列

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

【RT-DETR有效改进】Google | EfficientNetV1一种超轻量又高效的网络 (附代码 + 添加教程)

前言 大家好&#xff0c;我是Snu77&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持Re…

2024首更---Web Service 教程

Web Services 简介 Web Services 可使您的应用程序成为 Web 应用程序。 Web Services 通过 Web 进行发布、查找和使用。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对下面的知识有基本的了解&#xff1a; HTMLXML 如果您希望首先学习这些项目&#xff0c;请在…

蓝桥杯-dfs(一)

&#x1f4d1;前言 本文主要是【算法】——dfs使用的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#xff1…

RLC如何通过改变频率实现输出稳压

当开关频率工作在容性区域时&#xff0c;容抗抵消完感抗还有剩余&#xff0c;所以容抗感抗可以近似为一个容抗Cr,但加上频率的改变&#xff0c;容抗又可以近似为一个可调电阻 那又改如何控制频率&#xff0c;保持输出稳压&#xff1f; 当输入与输出电压不变时&#xff0c;Rac变…

Oracle 经典练习题 50 题

文章目录 一 CreateTable二 练习题1 查询"01"课程比"02"课程成绩高的学生的信息及课程分数2 查询"01"课程比"02"课程成绩低的学生的信息及课程分数3 查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4 查询平均成绩小于…

[小程序]API、数据与事件

一、API ①事件监听API 以on开头&#xff0c;用来监听事件的触发&#xff08;如wx.inWindowResize&#xff09; ②同步API 以Sync结尾&#xff0c;且可以通过函数返回值获取&#xff0c;执行错误会抛出异常&#xff08;如wx.setStorageSync&#xff09; ③异步API 类似网页中的…

yum下载源,vim使用

文章目录 yum本地配置lzrsz命令行互传scp(远程拷贝)vim yum本地配置 [rootiZf8z3j2ckkap6ypn717msZ ~]# pwd /root [rootiZf8z3j2ckkap6ypn717msZ ~]# ls /etc/yum.repos.d CentOS-Base.repo epel.repo //本地配置源yum会根据/etc/yum.repo.d路径下的配置文件来构成自己的下载…

pip安装之后还是无法使用问题处理

最近由于需要使用到Python 相关功能&#xff0c; 记录下一些入门小技巧 1 python 下载安装 在window10 环境下载免安装版本&#xff0c; 并解压 安装包下载地址&#xff1a; https://www.python.org/ftp/python/3.12.1/python-3.12.1-embed-amd64.zip 2. 安装pip, 由于是内嵌…

基于无锁循环队列的线程池的实现

目录 出处&#xff1a;B站码出名企路 应用场景 设计实现 等待策略模块 晚绑定 C 中的 override关键字 C中的 default 关键字 C中的 delete 关键字 C中的 explicit 关键字 C中 using 别名技巧 sleep 和 yield的区别 noexcept关键字 volatile关键字 无锁循环队列的…

【计算机网络】TCP握手与挥手:三步奏和四步曲

这里写目录标题 前言三次握手四次挥手三次握手和四次挥手的作用TCP三次握手的作用建立连接防止已失效的连接请求建立连接防止重复连接 TCP四次挥手的作用&#xff1a;安全关闭连接避免数据丢失避免半开连接 总结&#xff1a; 总结 前言 TCP&#xff08;传输控制协议&#xff09…

《游戏-02_2D-开发》

基于《游戏-01_2D-开发》&#xff0c; 继续制作游戏&#xff1a; 首先给人物添加一个2D重力效果 在编辑的项目设置中&#xff0c; 可以看出unity默认给的2D重力数值是-9.81&#xff0c;模拟现实社会中的重力效果 下方可以设置帧率 而Gravity Scale代表 这个数值会 * 重力 还…

MySQL---多表等级查询综合练习

创建emp表 CREATE TABLE emp( empno INT(4) NOT NULL COMMENT 员工编号, ename VARCHAR(10) COMMENT 员工名字, job VARCHAR(10) COMMENT 职位, mgr INT(4) COMMENT 上司, hiredate DATE COMMENT 入职时间, sal INT(7) COMMENT 基本工资, comm INT(7) COMMENT 补贴, deptno INT…

【cucumber】cluecumber-report-plugin生成测试报告

cluecumber为生成测试报告的第三方插件&#xff0c;可以生成html测报&#xff0c;该测报生成需以本地json测报的生成为基础。 所以需要在测试开始主文件标签CucumberOptions中&#xff0c;写入生成json报告。 2. pom xml文件中加入插件 <!-- 根据 cucumber json文件 美化测…

使用docker配置semantic slam

一.Docker环境配置 1.拉取Docker镜像 sudo docker pull ubuntu:16.04拉取的为ununtu16版本镜像&#xff0c;环境十分干净&#xff0c;可以通过以下命令查看容器列表 sudo docker images 如果想删除多余的docker image&#xff0c;可以使用指令 sudo docker rmi -f <id&g…

【深度学习目标检测】十七、基于深度学习的洋葱检测系统-含GUI和源码(python,yolov8)

使用AI实现洋葱检测对农业具有以下意义&#xff1a; 提高效率&#xff1a;AI技术可以快速、准确地检测出洋葱中的缺陷和问题&#xff0c;从而提高了检测效率&#xff0c;减少了人工检测的时间和人力成本。提高准确性&#xff1a;AI技术通过大量的数据学习和分析&#xff0c;能够…

【面试】java并发编程面试题

java并发编程面试题 何为进程?何为线程?JVM拓展为什么程序计数器、虚拟机栈和本地方法栈是线程私有的呢&#xff1f;为什么堆和方法区是线程共享的呢虚拟机栈和本地方法栈为什么是私有的?一句话简单了解堆和方法区单核 CPU 上运行多个线程效率一定会高吗&#xff1f;创建线程…

【机组】存储器、总线及堆栈寄存器实验的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 ​目录 &#x1f33a;一、 实验目的 …

【力扣hot100】二分查找

文章目录 Arrays.sort()时间复杂度o(n)二分法时间复杂度o(logn) 1.搜索插入位置代码 2. 搜索二维矩阵思路&#xff1a;代码&#xff1a; 34. 在排序数组中查找元素的第一个和最后一个位置思路&#xff1a;代码&#xff1a; 153. 寻找旋转排序数组中的最小值思路&#xff1a;代码…

5.2 基于深度学习和先验状态的实时指纹室内定位

文献来源 Nabati M, Ghorashi S A. A real-time fingerprint-based indoor positioning using deep learning and preceding states[J]. Expert Systems with Applications, 2023, 213: 118889.&#xff08;5.2_基于指纹的实时室内定位&#xff0c;使用深度学习和前一状态&…