初阶数据结构(C语言实现)——5.2 二叉树的顺序结构及堆的实现

news2025/3/14 20:05:43

1.二叉树的顺序结构及实现

1.1 二叉树的顺序结构

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

在这里插入图片描述
左图就是完全二叉树的数组存储
右图是非完全二叉树的数组顺序存储,会造成浪费
数组存储不适合,因为会浪费很多空间。数组存储表示二叉树只适合完全二叉树

  • 逻辑结构:想像出来的
  • 物理结构:实实在在的在内存中是如何存储

表示二叉树的值在数组位置中父子下标关系:

  • parent =(child-1)/2
  • leftchild = parent*2+1
  • rightchild = parent*2+2

1.2 堆的概念及结构

如果有一个关键码的集合K = { k0,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: ki<=k2*i+1 且 ki <=k2*i+2 ( ki>= k2*i+1且 ki>=k2*i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

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

在这里插入图片描述

小根堆:树中所有父亲都小于或等于孩子
大根堆:树中所有父亲都大于或等于孩子

1.2.1 堆的时间复杂度

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

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

1.3 堆的实现思路(均以大顶堆为例后面实现也是)

  • 难点:你实际操作的是数组,但是你要会画图,清晰的知道你实现的是二叉树。
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
//堆的初始化
void HeapInit(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDataType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDataType HeapTop(HP* php);
// 堆的数据个数
bool HeapEmpty(HP* php);
// 堆的判空
int HeapSize(HP* php);

1.3.0 堆的初始化

初始化,先断言,然后malloc开辟空间,初始化大小和容量

void HeapInit(HP* php)
{
	assert(php);//断言
	php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);//开辟初始空间
	if (php->a == NULL)
	{
		perror("HeapInit::malloc fail!");
		return;
	}
	php->size = 0;//目前堆内没有数据,所以size = 0
	php->capacity = 4;//默认初始容量是4
}

1.3.1 堆向上调整算法

  • 在数组上看起来是毫无章法的交换数据,但是把二叉树画出来,就清晰的知道为什么这么换。

在这里插入图片描述

从孩子向上调整,从刚刚插入的数据位置进行调整
结束条件,是我们的最后交换到堆顶,堆顶结点也就是父亲结点>=0,
在这里插入图片描述

  • 向上调整算法代码实现
void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType x = *p1;
	*p1 = *p2;
	*p2 = x;
}
 void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;//根据孩子位置,计算父亲位置
	while (child > 0)//child = 0,说明parent不存在了,已经到堆顶了
	{
		if (a[child] > a[parent]) //当孩子大小大于父亲大小的时候就交换
		{
			swap(&a[child], &a[parent]);//因为其他地方也要用到交换,封装了一个交换函数
			child = parent; //现在把父亲的值给孩子。
			parent = (child - 1) / 2;//继续计算现在节点的父亲结点,
		}
		else//孩子<=父亲 没必要往上走,直接break就行。		
		{
			break;
		}
	}
}

1.2.1 堆向下调整算法

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

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

在这里插入图片描述

void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType x = *p1;
	*p1 = *p2;
	*p2 = x;
}
void AdjustDown(HPDataType* a, int n ,int parent)
{
	int child = parent*2+1;//根据父亲位置,计算孩子位置
	while (child <n)//走到没有孩子的时候,结束,
	{
		// 选出左右孩子中大的那一个
		if (child + 1 < n && a[child + 1] > a[child])//必须把child+1<n放前面,否则后面a[child+1]就越界了!
		{
			++child;
		}
		//交换
		if (a[child] > a[parent])
		{
			swap(&a[child],&a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

1.2.4 堆的插入

先判断是否要扩容
然后进行数据插入
判断是否满足大根堆条件,不满足就要进行交换

  • 先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

在这里插入图片描述

  • 堆的插入代码实现
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("HeapPush::realloc fail!");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}

	php->a[php->size] = x;	//在数组尾巴插入数据
	php->size++;//给size++
	
	//封装一个调整函数,在函数内判断是否进行交换,交换就直接换。
	AdjustUp(php->a,php->size-1);
}

  • 代码验证,打开调试窗口,在监视1中输入ph.a,5,就能看到我们插入的堆的值。看着数组值自己手动画图,就能看到我们的大顶堆成功实现了。

在这里插入图片描述

1.2.5 堆的删除

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

在这里插入图片描述

  • 堆的删除代码实现
void HeapPop(HP * php)
{
	assert(php);
	assert(!HeapEmpty(php));
	//删除数据
	//(1)交换堆顶和最后一个数据 直接删掉。
	swap(&php->a[0], &php->a[php->size-1]);
	php->size--;
	//新的堆顶元素向下调整
	AdjustDown(php->a, php->size,0);
}

1.2.6 其他

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

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

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
  • 验证。调试,在监控窗口输入:hp.a,3

在这里插入图片描述

2 附录:堆的代码实现

2.1 20250312_heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

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

//堆的初始化
void HeapInit(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDataType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDataType HeapTop(HP* php);
// 堆的数据个数
bool HeapEmpty(HP* php);
// 堆的判空
int HeapSize(HP* php);

2.2 20250312_heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"20250312_heap.h"


void HeapInit(HP* php)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
	if (php->a == NULL)
	{
		perror("HeapInit::malloc fail!");
		return;
	}
	php->size = 0;
	php->capacity = 4;
}

void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType x = *p1;
	*p1 = *p2;
	*p2 = x;
}

 void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;//根据孩子位置,计算父亲位置
	while (child > 0)//child = 0,说明parent不存在了,已经到堆顶了
	{
		if (a[child] > a[parent]) //当孩子大小大于父亲大小的时候就交换
		{
			swap(&a[child], &a[parent]);//因为其他地方也要用到交换,封装了一个交换函数
			child = parent; //现在把父亲的值给孩子。
			parent = (child - 1) / 2;//继续计算现在节点的父亲结点,
		}
		else//孩子<=父亲 没必要往上走,直接break就行。		
		{
			break;
		}
	}
}

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("HeapPush::realloc fail!");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}

	php->a[php->size] = x;	//在数组尾巴插入数据
	php->size++;//给size++
	
	//封装一个调整函数,在函数内判断是否进行交换,交换就直接换。
	AdjustUp(php->a,php->size-1);
}



void AdjustDown(HPDataType* a, int n ,int parent)
{
	int child = parent*2+1;//根据父亲位置,计算孩子位置
	while (child <n)//走到没有孩子的时候,结束,
	{
		// 选出左右孩子中大的那一个
		if (child + 1 < n && a[child + 1] > a[child])//必须把child+1<n放前面,否则后面a[child+1]就越界了!
		{
			++child;
		}
		//交换
		if (a[child] > a[parent])
		{
			swap(&a[child],&a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP * php)
{
	assert(php);
	assert(!HeapEmpty(php));
	//删除数据
	//(1)交换堆顶和最后一个数据 直接删掉。
	swap(&php->a[0], &php->a[php->size-1]);
	php->size--;
	//新的堆顶元素向下调整
	AdjustDown(php->a, php->size,0);
}

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

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

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

2.3 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"20250312_heap.h"


int main()
{
    HP hp;
    HeapInit(&hp);
    HeapPush(&hp, 1);
    HeapPush(&hp, 24);
    HeapPush(&hp, 7);
    HeapPush(&hp, 9);
    HeapPush(&hp, 4);

    HeapPop(&hp);
    HeapPop(&hp);
    return 0;
}

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

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

相关文章

ArcGIS Pro 车牌分区数据处理与地图制作全攻略

在大数据时代&#xff0c;地理信息系统&#xff08;GIS&#xff09;技术在各个领域都有着广泛的应用&#xff0c;而 ArcGIS Pro 作为一款功能强大的 GIS 软件&#xff0c;为数据处理和地图制作提供了丰富的工具和便捷的操作流程。 车牌数据作为一种重要的地理空间数据&#xf…

文件解析漏洞靶场通关合集

一、IIS解析漏洞 &#xff08;一&#xff09;iis6的目录解析漏洞(.asp目录中的所有文件都会被当做asp文件执行) 第一步&#xff1a;在网站根目录下创建了一个x.asp文件夹&#xff0c;并在文件夹中创建一个名为1.txt的文本文档 第二步&#xff1a;文本文档中输入<% now()%&…

塔能IVO-SCY智能机箱:点亮智慧城市的电力“智慧核芯”

在智慧城市建设的宏大征程中&#xff0c;稳定且智能的电力供应犹如坚固基石&#xff0c;支撑着各类设备高效、稳定地运行。塔能科技的IVO-SCY智能机箱&#xff0c;凭借其卓越的电源管理系统&#xff0c;当之无愧地成为了整个智慧城市电力保障体系中的“智慧心脏”&#xff0c;源…

【Oracle】19c数据库控制文件多路径配置

一、关闭数据库&#xff08;2个节点实例都要关闭&#xff09; srvctl stop database -d ora19c 二、多路径控制文件 打开其中一个节点到nomount状态 sqlplus / as sysdba startup nomount; [oracleora19c1:/home/oracle]$ rman target / RMAN> restore controlfile to…

Android Media3 ExoPlayer 开发全攻略:从基础集成到高级功能实战

目录 1. 引言 2. 添加依赖 3. 初始化ExoPlayer并播放视频 3.1 XML 布局 3.2 初始化ExoPlayer 4. 控制播放 5. 监听播放状态 6. 播放网络流&#xff08;HLS / DASH / RTSP&#xff09; 7. ExoPlayer 进阶 7.1 手动切换功能 7.2 DRM 保护 8. 释放播放器资源 9. 从旧…

Trae与Builder模式初体验

说明 下载的国际版&#xff1a;https://www.trae.ai/ 建议 要选新模型 效果 还是挺不错的&#xff0c;遇到问题反馈一下&#xff0c;AI就帮忙解决了&#xff0c;真是动动嘴&#xff08;打打字就行了&#xff09;&#xff0c;做些小的原型效果或演示Demo很方便呀&#xff…

如何通过修改hosts文件、启动Apache服务器、修改httpd.conf文件、配置虚拟主机、创建站点目录和文件等步骤来配置虚拟主机并发布PHP站点

Web服务器配置——修改hosts文件&#xff0c;将域名解析到本地 核心内容&#xff1a;介绍了如何通过修改hosts文件来实现将任意域名解析到本地&#xff0c;以便在开发过程中使用自定义域名访问本地站点。步骤&#xff1a; 打开位于C:\Windows\System32\drivers\etc的hosts文件…

政策助力,3C 数码行业数字化起航

政策引领&#xff0c;数字经济浪潮来袭 在当今时代&#xff0c;数字经济已成为全球经济发展的核心驱动力&#xff0c;引领着新一轮科技革命和产业变革的潮流。我国深刻洞察这一发展趋势&#xff0c;大力推进数字化经济发展战略&#xff0c;为经济的高质量发展注入了强大动力。 …

MySQL数据库复制

文章目录 MySQL数据库复制一、复制的原理二、复制的搭建1.编辑配置文件2.在主库上创建复制的用户3.获取主库的备份4.基于从库的恢复5.建立主从复制6.开启主从复制7.查看主从复制状态 MySQL数据库复制 MySQL作为非常流行的数据库&#xff0c;支撑它如此出彩的因素主要有两个&am…

101.在 Vue 3 + OpenLayers 使用 declutter 避免文字标签重叠

1. 前言 在使用 OpenLayers 进行地图开发时&#xff0c;我们经常需要在地图上添加点、线、区域等图形&#xff0c;并给它们附加文字标签。但当地图上的标注较多时&#xff0c;文字标签可能会发生重叠&#xff0c;导致用户无法清晰地查看地图信息。 幸运的是&#xff0c;OpenL…

uniapp移动端图片比较器组件,仿英伟达官网rtx光追图片比较器功能

组件下载地址&#xff1a;https://ext.dcloud.net.cn/plugin?id22609 已测试h5和微信小程序&#xff0c;理论支持全平台 亮点&#xff1a; 简单易用 使用js计算而不是resize属性&#xff0c;定制化程度更高 组件挂在后可播放指示线动画&#xff0c;提示用户可以拖拽比较图片…

深度学习与大模型-矩阵

矩阵其实在我们的生活中也有很多应用&#xff0c;只是我们没注意罢了。 1. 矩阵是什么&#xff1f; 简单来说&#xff0c;矩阵就是一个长方形的数字表格。比如你有一个2行3列的矩阵&#xff0c;可以写成这样&#xff1a; 这个矩阵有2行3列&#xff0c;每个数字都有一个位置&a…

搭建基于chatgpt的问答系统

一、语言模型&#xff0c;提问范式与 Token 1.语言模型 大语言模型&#xff08;LLM&#xff09;是通过预测下一个词的监督学习方式进行训练的&#xff0c;通过预测下一个词为训练目标的方法使得语言模型获得强大的语言生成能力。 a.基础语言模型 &#xff08;Base LLM&…

LuaJIT 学习(2)—— 使用 FFI 库的几个例子

文章目录 介绍Motivating Example: Calling External C Functions例子&#xff1a;Lua 中调用 C 函数 Motivating Example: Using C Data StructuresAccessing Standard System FunctionsAccessing the zlib Compression LibraryDefining Metamethods for a C Type例子&#xf…

解锁 AI 开发的无限可能:邀请您加入 coze-sharp 开源项目

大家好&#xff01;今天我要向大家介绍一个充满潜力的开源项目——coze-sharp&#xff01;这是一个基于 C# 开发的 Coze 客户端&#xff0c;旨在帮助开发者轻松接入 Coze AI 平台&#xff0c;打造智能应用。项目地址在这里&#xff1a;https://github.com/zhulige/coze-sharp&a…

全面解析与实用指南:如何有效解决ffmpeg.dll丢失问题并恢复软件正常运行

在使用多媒体处理软件或进行视频编辑时&#xff0c;你可能会遇到一个常见的问题——ffmpeg.dll文件丢失。这个错误不仅会中断你的工作流程&#xff0c;还可能导致软件无法正常运行。ffmpeg.dll是FFmpeg库中的一个关键动态链接库文件&#xff0c;负责处理视频和音频的编码、解码…

Python----计算机视觉处理(opencv:像素,RGB颜色,图像的存储,opencv安装,代码展示)

一、计算机眼中的图像 像素 像素是图像的基本单元&#xff0c;每个像素存储着图像的颜色、亮度和其他特征。一系列像素组合到一起就形成 了完整的图像&#xff0c;在计算机中&#xff0c;图像以像素的形式存在并采用二进制格式进行存储。根据图像的颜色不 同&#xff0c;每个像…

小米路由器SSH下安装DDNS-GO

文章目录 前言一、下载&#xff06;安装DDNS-GO二、配置ddns-go设置开机启动 前言 什么是DDNS&#xff1f; DDNS&#xff08;Dynamic Domain Name Server&#xff09;是动态域名服务的缩写。 目前路由器拨号上网获得的多半都是动态IP&#xff0c;DDNS可以将路由器变化的外网I…

go语言zero框架拉取内部平台开发的sdk报错的修复与实践

在开发过程中&#xff0c;我们可能会遇到由于认证问题无法拉取私有 SDK 的情况。这种情况常发生在使用 Go 语言以及 Zero 框架时&#xff0c;尤其是在连接到私有平台&#xff0c;如阿里云 Codeup 上托管的 Go SDK。如果你遇到这种错误&#xff0c;通常是因为 Go 没有适当的认证…

手机屏幕摔不显示了,如何用其他屏幕临时显示,用来导出资料或者清理手机

首先准备一个拓展坞 然后 插入一个外接的U盘 插入鼠标 插入有数字小键盘区的键盘 然后准备一根高清线&#xff0c;一端链接电脑显示器,一端插入拓展坞 把拓展坞的连接线&#xff0c;插入手机充电口&#xff08;可能会需要转接头&#xff09; 然后确保手机开机 按下键盘…