数据结构:二叉树的顺序结构--堆

news2024/12/23 13:13:47

朋友们、伙计们,我们又见面了,本期来给大家解读一下栈和队列方面的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

目录

前言:

1.堆的概念及结构

2.堆的实现

2.1创建堆

2.2初始化堆

2.3向堆中插入数据

2.4向上调整算法 

2.5判断堆是否为空

2.6删除堆中的数据

2.7向下调整算法

2.8有效元素个数、堆顶元素

2.9代码测试 

2.10完整代码


前言:

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

1.堆的概念及结构

如果有一个关键码的集合K = {k_{0},k_{1},k_{2},k_{3}, ... ,k_{n-1}},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:K_{i}<=K_{2*i+1}K_{i}<=K_{2*i+2}(i = 0,1,2,3,4,......n-1)被称为小堆或K_{i}>=K_{2*i+1}K_{i}>=K_{2*i+2} (i = 0,1,2,3,4,......n-1)被称为大堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
图示:

简单的来讲:

1.大堆就是每一个孩子节点都要比它的父亲节点小,这只是规定了父亲节点和孩子节点之间的大小关系,并没有规定兄弟节点之间的关系,两个兄弟节点的大小不确定

2. 小堆就是每一个孩子节点都要比它的父亲节点大,这只是规定了父亲节点和孩子节点之间的大小关系,并没有规定兄弟节点之间的关系,两个兄弟节点的大小不确定

因此堆并不一定是有序的。

堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树

2.堆的实现

堆是一种特殊的完全二叉树,因此实现堆的思路就是使用顺序表,同样的我们也是分模块来写,创建头文件:Heap.h、源文件:Heap.c和Test.c,堆在这里主要实现的接口是:初始化、插入,删除(删除堆顶元素)、判空、有效元素个数、堆顶元素、销毁。话不多说我们直接开始:

注意:这里实现的是以小堆为例

2.1创建堆

堆的创建就是创建一个顺序表,然后再进行堆的一系列操作:

头文件:Heap.h

//创建堆
//顺序表
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size = 0;  //有效元素个数
	int capacity;  //容量
}Heap;

2.2初始化堆

这些接口实现都比较简单,直接上手即可:

头文件:Heap.h

//初始化
void HeapInit(Heap* php);

源文件:Heap.c

//初始化
void HeapInit(Heap* php)
{
	php->a = NULL;  
	php->size = 0;
	php->capacity = 0;
}

2.3向堆中插入数据

插入堆在这里插入的是堆的最后面,对应的也就是顺序表的最后一个位置,当插入进去之后还面临一个问题:我们创建的就是小堆,那么插入的数据如果小于它的父亲,就需要向上调整,如果调整之后还小于它新的父亲的,那么还是得继续向上调整,直到符合小根堆的逻辑

因此插入过程可以分为这么几步:

1. 先为堆开辟一块空间。

2. 检测容量,不够还需扩容。

3. 插入,判断是否需要进行向上调整。

头文件:Heap.h

//插入
void HeapPush(Heap* php, HPDataType x);

源文件:Heap.c

//插入
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	//插入数据的过程
	//容量不够则需要扩容
	//判断空间是否不足
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		//为堆开辟空间
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	//插入数据
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

向上调整算法我们再来分装一个函数

2.4向上调整算法 

向上调整我们的第一步首先要找它的父亲节点(在顺序表的逻辑下:如果它是左孩子,那么它的父亲节点的下标就是它的下标-1然后除2,如果它是右孩子,那么它的父亲节点的下标就是它的下标-2然后除2,但是呢?我们进行的是整数除法,没有余数,因此无论是左孩子还是右孩子都是使用-1除2的方法进行计算)。第二步判断孩子是否小于父亲,小于的话孩子和父亲就要交换位置,大于的话表示符合小堆逻辑,直接跳出即可,然后再让交换后的父亲节点变为新的孩子节点,然后再求新的父亲节点,依次类推,直到调整到堆顶,堆顶元素的下标为0,那么就表示孩子节点的下标为0时就不需要再调整了,退出即可:

 因此孩子与父亲之间的关系可以总结为:

int Child = Parent*2+1;   //左孩子
int Child = Parent*2+2;  //右孩子
int Parent = (Child-1)/2;  //父亲

源文件:Heap.c

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
	//父节点
	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;
		}
	}
}

2.5判断堆是否为空

堆为空的条件就是堆中的有效元素个数为0。

头文件:Heap.h

//判空
bool HeapEmpty(Heap* php);

源文件:Heap.c

//判空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

2.6删除堆中的数据

删除堆元素时并不是删除最后一个元素,这样删除并没有什么意义,因此删除堆中的元素时删除的是堆顶的元素,那么这里就有两种方法:

1. 和顺序表的头删一样,直接挪动数据进行覆盖,但是这样做时间复杂度高,而且挪动数据本来就是一件代价比较大的事情,再加上如果挪动数据进行覆盖的话就会将原本的父子关系打乱,父子关系打乱就有点扯淡了。

2. 将堆顶的数据和最后一个数据交换,然后删除掉最后一个数据,这时将剩下的元素进行向下调整。

所以删除数据可以分为这几步:

1. 先判断堆是否为空,为空就不能再删除。

2. 再交换顶部和最后的数据。

3. 再将最后一个元素删除。

4. 再判断是否需要向下调整。

头文件:Heap.h

//删除
void HeapPop(Heap* php);

源文件:Heap.c

void HeapPop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	//交换堆顶的数据和最后的数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除最后的数据
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}

向下调整算法我们再来分装一个函数

2.7向下调整算法

向下调整算法首先我们需要根据孩子与父亲的下标关系找到堆顶元素的左右孩子,然后找出左右孩子中最小的孩子,然后将其交换,然后再将孩子的位置更新为父节点,再继续找新的父亲节点的左右孩子的最小的节点,再进行交换,依次类推,直到交换到最后一个孩子节点(孩子节点小于等于总结点个数)。

找左右孩子中的最小的我们可以使用假设法,假设左孩子为最小的,然后进行判断,如果不合适,就给左孩子加1,从而得到右孩子,这里还需要注意,如果没有右孩子呢?那就只能和左孩子比较,也就不需要进行比较了(左孩子加一小于等于总结点个数)。

所以我们在设置向下调整算法的时候需要传堆、元素个数、堆顶的下标。

 源文件:Heap.c

//向下调整
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++;   //若不符合就转化为右孩子
		}
		//判断孩子和父亲的大小关系
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//更新父亲和孩子节点
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

2.8有效元素个数、堆顶元素

1. 返回堆顶元素时先要判断堆是否为空,堆顶元素的下标就是0,因此直接返回下标为0的元素。

2. 有效元素的个数就是顺序表中的size,直接返回即可。

头文件:Heap.h

//有效元素个数
int HeapSize(Heap* php);

//获取堆顶元素
HPDataType HeapTop(Heap* php);

源文件:Heap.c

//有效元素个数
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

//获取堆顶元素
HPDataType HeapTop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	return php->a[0];
}

2.9代码测试 

插入测试:

写完了堆的基本接口之后我们可以调试通过监视窗口来观察一下,我们将一个数据依次插入到堆中,观察他们的变化:

源文件:Test.c

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

}

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

 

删除测试: 

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

	//删除
	HeapPop(&hp);

}

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

 

2.10完整代码

头文件: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;  //容量
}Heap;

//初始化
void HeapInit(Heap* php);

//销毁
void HeapDestroy(Heap* php);

//插入
void HeapPush(Heap* php, HPDataType x);

//删除
void HeapPop(Heap* php);

//判空
bool HeapEmpty(Heap* php);

//有效元素个数
int HeapSize(Heap* php);

//获取堆顶元素
HPDataType HeapTop(Heap* php);

源文件:Heap.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

//初始化
void HeapInit(Heap* php)
{
	php->a = NULL;  
	php->size = 0;
	php->capacity = 0;
}

//销毁
void HeapDestroy(Heap* php)
{
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
	//父节点
	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(Heap* php, HPDataType x)
{
	assert(php);
	//插入数据的过程
	//容量不够则需要扩容
	//判断空间是否不足
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		//为堆开辟空间
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	//插入数据
	php->a[php->size] = x;
	php->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++;   //若不符合就转化为右孩子
		}
		//判断孩子和父亲的大小关系
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//更新父亲和孩子节点
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//删除
void HeapPop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	//交换堆顶的数据和最后的数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除最后的数据
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}


//判空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

//有效元素个数
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

//获取堆顶元素
HPDataType HeapTop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	return php->a[0];
}


源文件:Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

	//删除
	//HeapPop(&hp);

}

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

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!

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

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

相关文章

代码随想录算法训练营day50 | 123.买卖股票的最佳时机III,188.买卖股票的最佳时机IV

代码随想录算法训练营day50 | 123.买卖股票的最佳时机III&#xff0c;188.买卖股票的最佳时机IV 123.买卖股票的最佳时机III解法一&#xff1a;动态规划 188.买卖股票的最佳时机IV解法一&#xff1a;动态规划 总结 123.买卖股票的最佳时机III 教程视频&#xff1a;https://www…

【NovelAI 小说SD批量生成 视频克隆】环境配置和使用方法

【样品】《我在东北立堂口》图生图半自动版SD一键成片 操作步骤&环境配置地址&#xff1a; 【NovelAI】月产10000全自动批量原创小说短视频支持文生图和视频克隆 该文章面向购买脚本的付费用户&#xff0c;提供所有问题以及解决办法。使用 notepad 打开对应的文件即可&…

光敏晶体管(ALS-PT19-315C/L177/TR8) 光照度和电压,电流关系分析.

背景 当我们使用光敏晶体管进行,测算光照度时,大多使用ADC电路测到电压. 那么怎么根据这个电压计算出对应具体的光照度呢? 下面将以 ALS-PT19-315C/L177/TR8 型号的 光敏晶体管为例,来进行分析介绍,并给出 如何根据最大光照度范围 选定合适的电阻和电容. 1,直接看数据手册给…

如何快速录制电脑屏幕?教您一键录屏的3种方法!

案例&#xff1a;如何快速录制电脑屏幕&#xff1f; 【打开录屏工具录制电脑屏幕&#xff0c;需要耗费一定的时间。有没有方法可以快速打开电脑录屏工具&#xff0c;实现一键录屏&#xff1f;】 随着互联网的发展和普及&#xff0c;电脑屏幕录制已经成为了一项必要的工作技能…

R语言实践——rWCVP入门

rWCVP入门 介绍1. 访问到WCVP1.1 方法一1.2 方法二&#xff08;谨慎&#xff09; 2. WCVP数据筛选2.1 关于按分类单元筛选的说明2.2 关于按分布区域筛选的说明 笔者实践 介绍 世界维管植物名录&#xff08;WCVP&#xff09;是维管植物物种的全球共识。它提供了科学已知的> …

Flink用户自定义连接器(Table API Connectors)学习总结

文章目录 前言背景官网文档概述元数据解析器运行时的实现 自定义扩展点工厂类Source扩展Sink和编码与解码 自定义flink-http-connectorSQL示例具体代码pom依赖HttpTableFactoryHttpTableSourceHttpSourceFunctionHttpClientUtil 最后参考资料 前言 结合官网文档和自定义实现一…

Leetcode | 1534 统计好三元组

1534 统计好三元组 文章目录 [1534 统计好三元组](https://leetcode.cn/problems/count-good-triplets/description/)题目解法1 暴力穷举解法2 枚举优化 题目 给你一个整数数组 arr &#xff0c;以及 a、b 、c 三个整数。请你统计其中好三元组的数量。 如果三元组 (arr[i], a…

怎么把录音转文字?推荐你这三款工具

随着科技不断发展&#xff0c;录音转文字的技术也逐渐被广泛应用于各种场景中。其中最常见的一种就是会议记录。在日常工作中&#xff0c;会议是企业和组织中必不可少的一个环节&#xff0c;但在会议过程中的录音和记录往往需要花费大量的时间和精力。这个时候&#xff0c;我们…

【AI聊天丨 ChatGPT应用案例一】— 仅用30分钟,ChatGPT帮你完成专利交底书!

Hi&#xff0c;大家好&#xff0c;我是零点壹客&#xff0c;今天主要也是想和大家一起唠唠ChatGPT&#xff0c; 尤其这两个月&#xff0c;ChatGPT出奇的火&#xff0c;想必各位圈友们或多或少的都已经有些了解。 ChatGPT的出现很大程度上已经改变了我们的工作方式&#xff0c;尤…

GPT4限制被破解!ChatGPT实现超长文本处理的新方法

目录 前言 使用chat-gpt过程中有哪些痛点 1.无法理解人类情感和主观性 2.上下文丢失 3.约定被打断 那如何去解决这个痛点 Transformer&#xff08;RMT&#xff09;怎么去实现的 1.Transformer 模型 2.RMT模型 3.计算推理速率 4.渐进学习能力 总结 写到最后 大家好…

DeepPyramid:在白内障手术视频中实现金字塔视图和可变形金字塔接收的语义分割

文章目录 DeepPyramid: Enabling Pyramid View and Deformable Pyramid Reception for Semantic Segmentation in Cataract Surgery Videos摘要本文方法模块细节 实验结果 DeepPyramid: Enabling Pyramid View and Deformable Pyramid Reception for Semantic Segmentation in …

探秘 | 简说IP地址以及路由器的功能究竟是什么?

我们都知道我们在上网的时候都有一个IP地址&#xff0c;用来和其他人进行通信和数据交换。 其中IP地址又分为内网地址和外网地址&#xff0c;也叫作私有地址和公有地址。 为什么要区分私有地址和公有地址呢&#xff1f;原因很简单&#xff0c;因为公有的IP地址不够使用了&…

UnityVR--组件4--Ray/Raycast/Linecast/OverlapSphere

目录 Ray/Raycast/Linecast//OverlapSphere简介 Ray类 Physics.Raycast方法 应用1&#xff1a;实现鼠标点击出射线并检测物体 应用2&#xff1a;实现鼠标点击拖拽物体 Physics.Linecast和Physics.OverlapSphere 应用3&#xff1a;进入范围时触发攻击 Ray/Raycast/Lineca…

day13 网络编程Tomcat服务器

c/s架构和b/s架构的区别 c/s架构:客户端软件,直观,体验好,界面美观,安全性高 b/s架构:浏览器–>服务器,可移植性好,开发和维护性好 网络访问的三要素:ip,端口,协议 udp协议和tcp协议的区别 udp协议:只管发送,不管发送到哪里,是否能不能接收,一对多,无连接通信协议 ​ …

蓝桥:前端开发笔面必刷题——Day3 数组(三)

文章目录 &#x1f4cb;前言&#x1f3af;两数之和 II&#x1f4da;题目内容✅解答 &#x1f3af;移除元素&#x1f4da;题目内容✅解答 &#x1f3af;有序数组的平方&#x1f4da;题目内容✅解答 &#x1f3af;三数之和&#x1f4da;题目内容✅解答 &#x1f4dd;最后 &#x…

混沌演练实践(二)-支付加挂链路演练 | 京东云技术团队

1. 背景 当前微服务架构下&#xff0c;各个服务间依赖高&#xff0c;调用关系复杂&#xff0c;业务场景很少可以通过一个系统来实现&#xff0c;常见的业务场景实现基本涉及多个上下游系统&#xff0c;要保证整体链路的稳定性&#xff0c;需要尽量减少系统之间的耦合性&#x…

Elasticsearch与Clickhouse数据存储对比 | 京东云技术团队

1 背景 京喜达技术部在社区团购场景下采用JDQFlinkElasticsearch架构来打造实时数据报表。随着业务的发展Elasticsearch开始暴露出一些弊端&#xff0c;不适合大批量的数据查询&#xff0c;高频次分页导出导致宕机、存储成本较高。 Elasticsearch的查询语句维护成本较高、在聚…

CloudBase CMS的开发注意事项

引言 在进行基于云开发的微信小程序开发时为了减轻工作量打算用CloudBase CMS来减轻工作量&#xff0c;随后去了解并体验了CloudBase CMS的使用&#xff0c;总体来说还有些许问题没有解决&#xff0c;对减轻后台管理工作并没有起到很大的作用。 项目情景 使用CloudBase CMS来管…

Flutter 笔记 | Flutter 基础组件

Text Text 用于显示简单样式文本&#xff0c;它包含一些控制文本显示样式的一些属性&#xff0c;一个简单的例子如下&#xff1a; Text("Hello world",textAlign: TextAlign.left, );Text("Hello world! Im Jack. "*4,maxLines: 1,overflow: TextOverflo…

HACKABLE: III

文章目录 HACKABLE: III实战演练一、前期准备1、相关信息 二、信息收集1、端口扫描2、访问网站3、查看网站源码4、扫描目录5、访问网址6、查看并下载7、访问网站8、查看文件9、解密10、访问网站11、访问网站12、查看文件13、解密14、访问网站15、访问网站16、下载图片17、隐写1…