数据结构【二叉树——堆】

news2025/1/19 7:50:01

二叉树——堆

  • 1.二叉树的概念与性质
    • 二叉树的概念
    • 特殊的二叉树
  • 2.二叉树的性质
  • 3.二叉树的存储结构
    • 顺序结构
    • 链式结构
  • 4.堆
    • 堆的概念
    • 堆接口的实现(默认为大堆)
      • 堆的结构
      • 堆的初始化
      • 堆的销毁
      • 栈的插入
      • 堆的删除
      • 取堆顶数据
      • 堆的元素个数
      • 堆的判空
    • 完整代码
      • Heap.h
      • Heap.c(实现部分)
      • test.c(测试部分)
  • 结语

1.二叉树的概念与性质

二叉树的概念

二叉树也是树的一种,但是他最多有两个子树(度最大为2),一颗二叉树是结点的有限集合,
这个集合要么为空,要么就是由一个根节点加上被称为左子树和右子树的二叉树组成。
在这里插入图片描述
从上图可以看出:

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

注:下图都是二叉树
在这里插入图片描述

特殊的二叉树

  • 满二叉树:当每一层的结点数都是最大的时候,这颗二叉树就是一颗满二叉树;假设这块树的高度为 h,那么这颗树最后一层的结点数量就为2h-1个,结点总数量为2h -1在这里插入图片描述
  • 完全二叉树:他的前 h-1层都是满的,最后一层不一定满(1~2h-1个);此外最后一层的结点必须是从左到右连续的。需要注意的是满二叉树是一种特殊的完全二叉树。
    在这里插入图片描述

2.二叉树的性质

以下性质的根节点的层数都为1

  1. 一颗非空二叉树在第i层最多有2i-1 个结点。
  2. 假设一颗二叉树的深度为h,那么这颗二叉树的最大结点数为2h-1
  3. 对任意一颗二叉树,如果度为0的叶子结点的个数为 n 0 n_0 n0,度为2的分支结点的个数为 n 2 n_2 n2,就会有 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1

假设二叉树有N个结点
那么从总结点的角度考虑: N = n 0 + n 1 + n 2 N = n_0 + n_1 + n_2 N=n0+n1+n2( n 1 n_1 n1代表度为1的分支结点)


从边的角度,N个结点的任意二叉树,总共有N-1条边
因为二叉树的每个结点都是父节点,而根结点没有父节点,每个结点与父节点之间有联系(也就是边),所以N个结点的二叉树一共有N-1条边


因为度为0的结点没有孩子,所以度为0的结点不产生边;度为1的结点只有一个孩子,所以每个度为1的结点产生一条边;度为2的结点有两个孩子,所以每个度为2的结点产生两条边,所以总边数为: n 1 + n 2 ∗ 2 n_1 + n_2 * 2 n1+n22
由于N个结点的任意二叉树,总共有N-1条边
所以从边的角度考虑: N − 1 = n 1 + n 2 ∗ 2 N-1 = n_1 + n_2 * 2 N1=n1+n22


结合总结点与边的两个公式得出 n 0 + n 1 + n 2 = n 1 + n 2 ∗ 2 + 1 n_0 + n_1 + n_2 = n_1 + n_2 * 2 +1 n0+n1+n2=n1+n22+1
即为 n 0 = n 2 + 1 n_0 = n_2+1 n0=n2+1

4.具有n个结点的满二叉树那么他的深度为 h = l o g 2 ( n + 1 ) h=log_2(n+1) h=log2(n+1)

总结点为 n = 2 h − 1 n=2^h-1 n=2h1
n + 1 = 2 h n+1=2^h n+1=2h
l o g 2 ( n + 1 ) = h log_2(n+1) = h log2(n+1)=h -> h = l o g 2 ( n + 1 ) h=log_2(n+1) h=log2(n+1)

5.对于具有n个节点的完全二叉树,如果是按照从上到下、从左到右的数组顺序对所有结点从0开始进行编号,则序号为i 的结点有:

  1. i>0,i位置结点的父节点序号为:(i - 1) / 2;i = 0,则 i 为根结点,无父节点
  2. 若2 * i + 1 < n,则左孩子序号为:2 * i + 1若2 * i + 1 >= n,则 i 结点无左孩子
  3. 若2 * i + 2 < n,则右孩子序号为:2 * i + 2若2 * i + 2 >= n,则 i 结点无右孩子

3.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链表结构。

顺序结构

顺序结构存储就是用数组来存储,但只适合表示完全二叉树,因为完全二叉树能填满数组,非完全二叉树总是会有一块空间不存放值,就会造成空间浪费。二叉树顺序存储在物理结构上是一个数组,在逻辑结构上是一颗二叉树。
在这里插入图片描述

链式结构

链式结构存储是指用链表来表示一颗二叉树,即用链表来表示逻辑关系,一个二叉树结点分为数据域和左右指针域,左右指针分别指向左右孩子所在的结点的地址。
链式结构分为二叉链和三叉链,三叉链是多一个指向父节点的指针。
在这里插入图片描述

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* parent; // 指向当前结点的双亲
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
}

4.堆

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

堆的概念

  1. 堆是一个完全二叉树
  2. 堆分大小堆

大堆:根结点的元素最大,父结点大于子节点
小堆:根结点的元素最小,父结点小于子结点

注意:堆并不是有序的,堆仅仅约束了父亲与孩子之间的大小关系,并没有约束兄弟之间的关系

堆接口的实现(默认为大堆)

因为是用数组来实现,所以大体和栈、顺序表的实现是一样的,所以我们的重心会放在插入和删除上

堆的结构

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

堆的初始化

// 堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

堆的销毁

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

栈的插入

插入有三种情况

  1. 插入的数比父亲小
  2. 插入的数比父亲大但是比祖宗大
  3. 插入的数比所有值都大

第一种情况堆就不需要调整,其他两种情况就需要向上调整
向上调整算法就是当我插入的数据比父亲大,就交换数据,直到成为了根或者是父亲比孩子大,这样一直循环下去就能每次把最大的数往上抬。

//交换函数
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)//当孩子成了根,就跳出循环
	{
		if (a[parent] < a[child])//当父亲小于孩子
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else//父亲大于孩子,不用再调整了
		{
			break;
		}
	}
}

插入代码:

//交换函数
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->_size == hp->_capacity)
	{
		int NewCapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->_a, NewCapacity*sizeof(HPDataType));
		if (tmp == 0)
		{
			perror("realloc fail");
			return;
		}
		hp->_capacity = NewCapacity;
		hp->_a = tmp;
	}
	hp->_a[hp->_size++] = x;
	AdjustUp(hp->_a, hp->_size - 1);
}

堆的删除

我们删除堆底的数据是没有意义,删除堆都是删除对顶的数据(堆顶要么最大要么最小),将堆顶数据与最后一个数据进行交换,再进行向下调整算法。
向下调整算法我们是从父亲结点开始往下调整,看左右孩子哪个更大,就与哪个孩子交换,直到没有成为叶子结点或者比孩子都大,这样我们删除最大的数据之后,就可以把次大的数据冒到堆顶。

//交换函数
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//向下调整算法
void AdjusDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//假设法
		//先假设左孩子大,当右孩子大于左孩子的时候,child++变更成右孩子
		if (child+1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	//先删除
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	//再调整
	AdjusDown(hp->_a, hp->_size, 0);
}

取堆顶数据

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->_a[0];
}

堆的元素个数

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}

堆的判空

// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

完整代码

Heap.h

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

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;
// 堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

Heap.c(实现部分)

#include"Heap.h"

//交换函数
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

// 堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->_size == hp->_capacity)
	{
		int NewCapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->_a, NewCapacity*sizeof(HPDataType));
		if (tmp == 0)
		{
			perror("realloc fail");
			return;
		}
		hp->_capacity = NewCapacity;
		hp->_a = tmp;
	}
	hp->_a[hp->_size++] = x;
	AdjustUp(hp->_a, hp->_size - 1);
}

//向下调整算法
void AdjusDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	AdjusDown(hp->_a, hp->_size, 0);
}

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->_a[0];
}

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}

// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

test.c(测试部分)

#include"Heap.h"

void TestHeap()
{
	int a[] = { 5,6,7,9,1,3,4,2,8,10 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);
	}
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	HeapDestory(&hp);

}

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

结语

最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言,也可以前往我的主页看更多好文哦(点击此处跳转到主页)。
如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!

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

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

相关文章

ArcGIS for js 4.x 加载图层

二维&#xff1a; 1、创建vue项目 npm create vitelatest 2、安装ArcGIS JS API依赖包 npm install arcgis/core 3、引入ArcGIS API for JavaScript模块 <script setup> import "arcgis/core/assets/esri/themes/light/main.css"; import Map from arcgis…

计网期末复习指南(五):运输层(可靠传输原理、TCP协议、UDP协议、端口)

前言&#xff1a;本系列文章旨在通过TCP/IP协议簇自下而上的梳理大致的知识点&#xff0c;从计算机网络体系结构出发到应用层&#xff0c;每一个协议层通过一篇文章进行总结&#xff0c;本系列正在持续更新中... 计网期末复习指南&#xff08;一&#xff09;&#xff1a;计算机…

【Go语言精进之路】构建高效Go程序:零值可用、使用复合字面值作为初值构造器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 引言一、深入理解并利用零值提升代码质量1.1 深入Go类型零值原理1.2 零值可用性的实践与优势1.2.1 切片(Slice)的零值与动态扩展1.2.2 Map的零值与安全访问1.2.3 函数参数与零值 二、使用复合字面值作为初值构造器2.1 结构体…

KT1404A语音芯片USB连电脑,win7正常识别WIN10无法识别USB设备

一、简介 KT1404A语音芯片画的板子&#xff0c;USB连接电脑&#xff0c;win7可以正常识别到U盘&#xff0c;WIN10提示无法识别USB设备&#xff08;获取设备描述符失败&#xff09;&#xff0c;这是什么问题 问题 首先&#xff0c;这款芯片已经出货非常非常多了&#xff0c;所…

【已有项目版】uniapp项目发版pda -- Android Studio

必备资料清单&#xff1a; 构建完成的app项目 在HBuilderX开发的uniapp项目 .keystore文件 文章目录 1. 安装Android Studio&#xff1a;https://developer.android.google.cn/studio?hlzh-cn2. 安装Android 离线SDK&#xff1a;https://nativesupport.dcloud.net.cn/AppDocs…

vs2013 - 打包

文章目录 vs2013 - 打包概述installshield2013limitededitionMicrosoft Visual Studio 2013 Installer Projects选择哪种来打包? 笔记VS2013打包和VS2019打包的区别打包工程选择view打包工程中单击工程名称节点&#xff0c;就可以在属性框中看到要改的属性(e.g. 默认是x86, 要…

全面分析找不到msvcr120.dll,无法继续执行程序问题

在计算机使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中“找不到msvcr120.dll”就是常见的一种。那么&#xff0c;找不到msvcr120.dll是什么意思呢&#xff1f; 一&#xff0c;msvcr120.dll文件概述 msvcr120.dll 是 Microsoft Visual C Redistributable …

AI大模型在广告领域的应用

深度对谈&#xff1a;广告创意领域中AIGC的应用_生成式 AI_Tina_InfoQ精选文章

多家加密数字交易所“弃牌”!香港虚拟资产正式进入监管新时代

一、华夏时报的采访内容 6月1日&#xff0c;香港证监会披露HKbitEX、PantherTrade、Accumulus等共11家平台&#xff0c;被当作获发牌的申请者&#xff08;deemed-to-be-licensed&#xff09;。知名的离岸交易所中只有Cryptocom获得批准&#xff0c;此前OKX、HTX、Bybit、Gate …

数据结构与算法笔记:基础篇 - 散列表(下):为什么散列表和链表经常会一起使用?

概述 已经学习了这么多章节了&#xff0c;你有没有发现&#xff0c;两种数据结构&#xff0c;散列表和链表&#xff0c;经常会被放在一起使用。你还记得&#xff0c;前面的章节中都有哪些地方讲到散列表和链表的组合使用吗&#xff1f; 在链表那一节&#xff0c;我讲到如何用…

AI Agentic Design Patterns with AutoGen(下):工具使用、代码编写、多代理群聊

文章目录 四、工具使用: 国际象棋游戏4.1 准备工具4.2 创建两个棋手代理和棋盘代理4.3 注册工具到代理4.4 创建对话流程&#xff0c;开始对话4.5 增加趣味性&#xff1a;加入闲聊 五、代码编写&#xff1a;财务分析5.1导入和配置代码执行器5.2 创建 代码执行/编写 代理5.3 定义…

【Golang】探索进程资源监控的精妙细节:利用Gopsutil/Process实现高级进程性能和资源信息监控

【Golang】探索进程资源监控的精妙细节&#xff1a;利用Gopsutil/Process实现高级进程性能和资源信息监控 大家好 我是寸铁&#x1f44a; 总结了一篇【Golang】探索进程资源监控的精妙细节&#xff1a;利用Gopsutil/Process实现高级进程性能和资源信息监控的文章✨ 喜欢的小伙伴…

dp+矩阵快速幂,CF551D. GukiZ and Binary Operations

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 551D - Codeforces 二、解题报告 1、思路分析 今天LATEX怎么不好用了 数据量很大&#xff0c;应该要用O(log) 或者 O(1)的的算法 按位考虑进行dp&#xff0c;计算k每位的方案数累乘即可&#x…

全光网络与传统网络架构的对比分析

随着信息技术的飞速发展&#xff0c;网络已经成为我们日常生活中不可或缺的一部分。在这个信息爆炸的时代&#xff0c;全光网络和传统网络架构作为两种主流的网络技术&#xff0c;各有其特点和适用范围。本文将对这两种网络架构进行详细的对比分析&#xff0c;帮助读者更好地了…

【sklearn】【逻辑回归1】

学习笔记来自&#xff1a; 所用的库和版本大家参考&#xff1a; Python 3.7.1Scikit-learn 0.20.1 Numpy 1.15.4, Pandas 0.23.4, Matplotlib 3.0.2, SciPy 1.1.0 1 概述 1.1 名为“回归”的分类器 在过去的四周中&#xff0c;我们接触了不少带“回归”二字的算法&#xf…

K-BAT01,K-CU01和利时卡件

K-BAT01,K-CU01和利时卡件。现场控制站下装与在线调试。9二、组态流程&#xff1a;操作站组态控制站组态新建工程控制站用户组态历史站组态下装现场控制站下装历史站下装操作员站10三、组态详解&#xff1a;1、K-BAT01,K-CU01和利时卡件。新建工程&#xff1a;打开工程总控&…

深度学习Week16——数据增强

文章目录 深度学习Week16——数据增强 一、前言 二、我的环境 三、前期工作 1、配置环境 2、导入数据 2.1 加载数据 2.2 配置数据集 2.3 数据可视化 四、数据增强 五、增强方式 1、将其嵌入model中 2、在Dataset数据集中进行数据增强 六、训练模型 七、自定义增强函数 一、前言…

什么时候用C而不用C++?

做接口只用C&#xff0c;千万别要C。C是编译器敏感的&#xff0c;一旦导出的接口里有 std::string这些东西&#xff0c;以及类&#xff0c;注定了要为各个编译器的各个版本准备独立的库。 刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C的资料从专业入门…

[AIGC] SpringBoot的自动配置解析

下面是一篇关于SpringBoot自动配置的文章&#xff0c;里面包含了一个简单的示例来解释自动配置的原理。 SpringBoot的自动配置解析 Spring Boot是Spring的一个子项目&#xff0c;用于快速开发应用程序。它主要是简化新Spring应用的初始建立以及开发过程。其中&#xff0c;自动…

传统工科硕士想转嵌入式,时间够吗?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 零基础开始学&#xff0…