基于树型结构实现顺序结构堆

news2024/10/27 15:00:21

目录

        前言

        一、树

        1、树的概念与结构

        2、树的相关术语 

        3、二叉树 

        4、满二叉树

​        5、完全二叉树

​        6、二叉树的存储

                        1、顺序结构

                        2、链式结构 

         二、堆

        1、堆的结构

         2、堆的初始化

         3、入堆(大根堆)

         4、出堆(大根堆)

         5、判断堆是否为空

         6、取堆顶元素

         7、获取堆中元素个数

 三、整体代码


前言

        树型结构是实现堆的基础,因此前面此篇会先介绍树,然后基于树型结构实现顺序结构堆。因为此篇完全基于自己的理解编写,所以如果出现一些错误还望大家指出,谢谢!


一、树

        1、树的概念与结构

                树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合
  1. 树中有且只有一个特殊的节点,称为根节点,根节点没有前驱节点
  2. 除根结点外,其余结点被分成 M(M>0) 互不相交的集合,其中每⼀个集合⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继节点

【注意】
  1. ⼦树是不相交的(如果存在相交就是图了,图以后得课程会有讲解)
  2. 除了根结点外,每个结点有且仅有⼀个⽗结点
  3. ⼀棵N个结点的树有N-1条边

        2、树的相关术语 

  1. ⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:1 是 2 的⽗结点
  2. ⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图: 2 是1 的孩⼦结点
  3. 结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如 1 的度为3,4 的度为2,9 的度为0
  4. 树的度:⼀棵树中,最⼤的结点的度称为树的度; 如上图:树的度为 3
  5. 叶⼦结点/终端结点:度为 0 的结点称为叶结点; 如上图: 5、6、7、8... 等结点为叶结点
  6. 分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: 2、3、4... 等结点为分⽀结点
  7. 兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟); 如上图: 2、3 是兄弟结点
  8. 结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;
  9. 树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为 3
  10. 结点的祖先:从根到该结点所经分⽀上的所有结点;如上图: 1 是所有结点的祖先
  11. 路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如1到 8 的路径为: 1-4-8;
  12. ⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是1的⼦孙
  13. 森林:由 mm>0) 棵互不相交的树的集合称为森林

        3、二叉树 

                二叉树是树型结构中一种特殊树,二叉树⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空

        二叉树的特点

  1. ⼆叉树不存在度⼤于 2 的结点
  2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树

        4、满二叉树

                满二叉树是一种特殊的二叉树,满二叉树的每一层节点都到达最大个数,也就是⼆叉树的层数为 K ,且结点总数 2^K - 1 ,则它就是满⼆叉树 

         5、完全二叉树

                完全二叉树是由满二叉树引导出来的,也就是满二叉树是一种特殊的完全二叉树。完全二叉树除最后一层外的每一层都必须是满节点数,并且最后一层的节点必须要是从左至右依次放置

        完全二叉树的性质

  1. 若规定根结点的层数为 k ,则⼀棵⾮空⼆叉树的第 i 层上最多有 2^{i - 1} 个结点
  2. 若规定根结点的层数为 k ,则深度为 h 的⼆叉树的最⼤结点数是 2^{h}-1
  3. 若规定根结点的层数为 k ,具有 n 个结点的满⼆叉树的深度 h = log2 (n + 1)

         6、二叉树的存储

                ⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构

                 1、顺序结构

                        顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储

                2、链式结构 

                        ⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系 

//链式结构节点
struct node
{
    int val;//储存数据
    struct node* left;//左子树
    struct node* right;//右子树
};

 二、堆

        堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备其他的特性
        
        堆的特性
  1. 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值
  2. 堆总是⼀棵完全⼆叉树

         因为堆总是一颗完全二叉树,因此最好使用顺序结构来实现堆,完全⼆叉树更适合使⽤顺序结构存储

        顺序结构二叉树性质 

  1. 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
  2. 若 2i+1<n ,左孩⼦序号: 2i+1 , 2i+1>=n 否则⽆左孩⼦
  3. 若 2i+2<n ,右孩⼦序号: 2i+2 , 2i+2>=n 否则⽆右孩⼦

        1、堆的结构

//堆的结构
struct Heap
{
	int* heap;//堆
	int size;//堆的有效元素个数
	int capacity;//堆的容量
};

         2、堆的初始化

//堆的初始化
void HeapInit(struct Heap* heap)
{
	//初始化堆的大小为4,后续不够在扩容
	heap->capacity = 4;
	heap->size = 0;
	//创建堆数组
	int* tmp = (int*)malloc(sizeof(int) * heap->capacity);
	//是否创建失败
	if (tmp == NULL)
	{
		assert(tmp);
	}
	heap->heap = tmp;
}

         3、入堆(大根堆)

        时间复杂度:O( logn )

                 堆的插是将新数据插⼊到数组的尾上,再进⾏向上调整算法,直到满⾜堆

                向上调整算法最多调整树的深度次,具有 n 个结点的满⼆叉树的深度 h = log2 (n + 1),因此时间复杂度为O( logn )

        向上调整算法
  1. 先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
  2. 插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双亲往上调整到合适位置即可

        向上调整算法 (大根堆)

//向上调整算法(大根堆)
void AdjustUp(int* arr, int pos)
{
	//当前调整的位置不能是堆顶
	if (pos == 0)
	{
		return;
	}
	//寻找双亲节点
	int parents = (pos - 1) / 2;
	//当前位置与双亲节点进行比较
	//如果当前位置的数大于双亲节点,就进行交换,并且继续向上调整
	//如果当前位置的数小于双亲节点,表示堆已经构建好了
	if (arr[parents] < arr[pos])
	{
		//交换两个数位置
		sweap(&arr[parents], &arr[pos]);
		//继续向上调整
		AdjustUp(arr, parents);
	}
}

        入堆(大根堆)

//入堆(大根堆)
void HeapPush(struct Heap* heap, int x)
{
	//判断堆是否已经满了
	if (heap->size == heap->capacity)
	{
		//扩容至当前容量的两倍
		heap->capacity *= 2;
		//创建堆数组
		int* tmp = (int*)realloc(heap->heap, sizeof(int) * heap->capacity);
		//是否创建失败
		if (tmp == NULL)
		{
			assert(tmp);
		}
		heap->heap = tmp;
	}
	//入堆
	heap->heap[heap->size] = x;
	//向上调整算法,保持堆中大根堆的特性
	AdjustUp(heap->heap, heap->size);
	//堆中有效元素个数加一
	heap->size++;
}

         4、出堆(大根堆)

        时间复杂度:O( logn )

               删除堆是删除堆顶的数据, 删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏向下调整算法,直到满⾜堆的结构

                向下调整算法最多调整树的深度次,具有 n 个结点的满⼆叉树的深度 h = log2 (n + 1),因此时间复杂度为O( logn )

        向下调整算法 

  1. 左右⼦树必须是⼀个堆,才能调整
  2. 将堆顶元素与堆中最后⼀个元素进⾏交换
  3. 删除堆中最后⼀个元素
  4. 将堆顶元素向下调整到满⾜堆特性为⽌

 

        向下调整算法 (大根堆)

//向下调整算法(大根堆)
void AdjustDown(int* arr, int size, int pos)
{
	//左孩子位置
	int child = pos * 2 + 1;
	//向下调整算法,直到左孩子位置大于数组个数
	if (child < size)
	{
		//选出左右孩子中最大的那个孩子
		if (child + 1 < size && arr[child] < arr[child + 1])
		{
			child++;
		}
		//与当前位置进行比较
		//如果左右孩子中最大数大于当前位置的数,就进行交换,并且继续向下调整
		//如果左右孩子中最大数小于当前位置的数,表示堆已经调整好了
		if (arr[child] > arr[pos])
		{
			//交换两个数的位置
			sweap(&arr[pos], &arr[child]);
			//继续向下调整
			AdjustDown(arr, size, child);
		}
	}
}

         出堆(大根堆)

//出堆——删除堆顶数据(大根堆)
void HeapPop(struct Heap* heap)
{
	//堆不能为空
	if (HeapEmpty(heap))
	{
		assert("Heap is NULL!");
	}
	//将堆顶元素与数组最后一个元素进行交换
	sweap(&(heap->heap[0]), &(heap->heap[heap->size - 1]));
	//堆中有效元素个数减一
	heap->size--;
	//向下调整算法,保持堆中大根堆的特性
	AdjustDown(heap->heap, heap->size, 0);
}

         5、判断堆是否为空

        时间复杂度:O( 1 ) 

                直接判断堆中元素个数是否为0, 因此时间复杂度为O( 1 )

//判断堆中是否为空
bool HeapEmpty(struct Heap* heap)
{
	//判断堆中是否为空
	if (heap->size)
	{
		return false;
	}
	return true;
}

         6、取堆顶元素

        时间复杂度:O( 1 )  

                堆顶就是数组下标为0的位置,直接返回即可, 因此时间复杂度为O( 1 )

//获取堆顶元素
int HeapTop(struct Heap* heap)
{
	//堆不能为空
	if (HeapEmpty(heap))
	{
		assert("Heap is NULL!");
	}
	//获取堆顶元素
	return heap->heap[0];
}

         7、获取堆中元素个数

         时间复杂度:O( 1 )  

//堆中有效元素个数
int HeapSize(struct Heap* heap)
{
	//堆中有效元素个数
	return heap->size;
}

 三、整体代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>



//交换两个数的位置
void sweap(int* num1, int* num2)
{
	int tmp = *num1;
	*num1 = *num2;
	*num2 = tmp;
}
//向下调整算法(大根堆)
void AdjustDown(int* arr, int size, int pos)
{
	//左孩子位置
	int child = pos * 2 + 1;
	//向下调整算法,直到左孩子位置大于数组个数
	if (child < size)
	{
		//选出左右孩子中最大的那个孩子
		if (child + 1 < size && arr[child] < arr[child + 1])
		{
			child++;
		}
		//与当前位置进行比较
		//如果左右孩子中最大数大于当前位置的数,就进行交换,并且继续向下调整
		//如果左右孩子中最大数小于当前位置的数,表示堆已经调整好了
		if (arr[child] > arr[pos])
		{
			//交换两个数的位置
			sweap(&arr[pos], &arr[child]);
			//继续向下调整
			AdjustDown(arr, size, child);
		}
	}
}
//向上调整算法(大根堆)
void AdjustUp(int* arr, int pos)
{
	//当前调整的位置不能是堆顶
	if (pos == 0)
	{
		return;
	}
	//寻找双亲节点
	int parents = (pos - 1) / 2;
	//当前位置与双亲节点进行比较
	//如果当前位置的数大于双亲节点,就进行交换,并且继续向上调整
	//如果当前位置的数小于双亲节点,表示堆已经构建好了
	if (arr[parents] < arr[pos])
	{
		//交换两个数位置
		sweap(&arr[parents], &arr[pos]);
		//继续向上调整
		AdjustUp(arr, parents);
	}
}
//堆的结构
struct Heap
{
	int* heap;//堆
	int size;//堆的有效元素个数
	int capacity;//堆的容量
};
//堆的初始化
void HeapInit(struct Heap* heap)
{
	//初始化堆的大小为4,后续不够在扩容
	heap->capacity = 4;
	heap->size = 0;
	//创建堆数组
	int* tmp = (int*)malloc(sizeof(int) * heap->capacity);
	//是否创建失败
	if (tmp == NULL)
	{
		assert(tmp);
	}
	heap->heap = tmp;
}
//入堆(大根堆)
void HeapPush(struct Heap* heap, int x)
{
	//判断堆是否已经满了
	if (heap->size == heap->capacity)
	{
		//扩容至当前容量的两倍
		heap->capacity *= 2;
		//创建堆数组
		int* tmp = (int*)realloc(heap->heap, sizeof(int) * heap->capacity);
		//是否创建失败
		if (tmp == NULL)
		{
			assert(tmp);
		}
		heap->heap = tmp;
	}
	//入堆
	heap->heap[heap->size] = x;
	//向上调整算法,保持堆中大根堆的特性
	AdjustUp(heap->heap, heap->size);
	//堆中有效元素个数加一
	heap->size++;
}
//判断堆中是否为空
bool HeapEmpty(struct Heap* heap)
{
	//判断堆中是否为空
	if (heap->size)
	{
		return false;
	}
	return true;
}
//堆中有效元素个数
int HeapSize(struct Heap* heap)
{
	//堆中有效元素个数
	return heap->size;
}
//出堆——删除堆顶数据(大根堆)
void HeapPop(struct Heap* heap)
{
	//堆不能为空
	if (HeapEmpty(heap))
	{
		assert("Heap is NULL!");
	}
	//将堆顶元素与数组最后一个元素进行交换
	sweap(&(heap->heap[0]), &(heap->heap[heap->size - 1]));
	//堆中有效元素个数减一
	heap->size--;
	//向下调整算法,保持堆中大根堆的特性
	AdjustDown(heap->heap, heap->size, 0);
}
//获取堆顶元素
int HeapTop(struct Heap* heap)
{
	//堆不能为空
	if (HeapEmpty(heap))
	{
		assert("Heap is NULL!");
	}
	//获取堆顶元素
	return heap->heap[0];
}
int main() 
{
	struct Heap heap;
	HeapInit(&heap);
	HeapPush(&heap, 1);
	HeapPush(&heap, 2);
	HeapPush(&heap, 3);
	HeapPush(&heap, 4);
	HeapPush(&heap, 5);
	HeapPush(&heap, 6);
	HeapPush(&heap, 7);
	HeapPop(&heap);
	while (!HeapEmpty(&heap))
	{
		printf("%d ", HeapTop(&heap));
		HeapPop(&heap);
	}
	int i = 0;
	return 0;
}

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

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

相关文章

C++网络编程之C/S模型

C网络编程之C/S模型 引言 在网络编程中&#xff0c;C/S&#xff08;Client/Server&#xff0c;客户端/服务器&#xff09;模型是一种最基本且广泛应用的架构模式。这种模型将应用程序分为两个部分&#xff1a;服务器&#xff08;Server&#xff09;和客户端&#xff08;Clien…

Java审计对比工具JaVers使用

最近有个需求&#xff0c;需要将页面的内容生成excel或者word文档&#xff0c;而且每次的修改都需要生成新的版本&#xff0c;同时需要记录每次修改变化的内容。我们会把每次的修改的内容提交赋值给一个java对象&#xff0c;同时存储到数据库一条新数据&#xff0c;对应数据表一…

uniapp圆形波浪进度效果

uniapp圆形波浪进度效果 背景实现思路代码实现尾巴 背景 最近项目中有些统计的地方需要用到圆形的波浪进度效果&#xff0c;要求是根据百分比值然后在一个圆形内动态的展示一个波浪形的进度&#xff0c;看参考一下效果。 实现思路 这个效果看着挺复杂的&#xff0c;那么我们…

android——渐变色

1、xml的方式实现渐变色 效果图&#xff1a; xml的代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <shape xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools…

索引的使用和优化

索引就是一种快速查询和检索数据的数据结构&#xff0c;mysql中的索引结构有&#xff1a;B树和Hash。 索引的作用就相当于目录的作用&#xff0c;我么只需先去目录里面查找字的位置&#xff0c;然后回家诶翻到那一页就行了&#xff0c;这样查找非常快&#xff0c; 创建一个表结…

【数据结构】宜宾大学-计院-实验五

实验五 栈和队列&#xff08;队列的基本操作&#xff09; 实验目的&#xff1a;链表结点结构&#xff1a;实验结果&#xff1a;运行截图&#xff1a;代码实现&#xff1a; 实验目的&#xff1a; 1&#xff0e;掌握队列的顺序存储结构和链式存储结构 2&#xff0e;实现队列的基…

(北京政务服务满意度公司)满意度调查助力服务质量提升

在当今社会&#xff0c;&#xff08;政务服务满意度公司&#xff09;政务窗口服务的质量直接关系到市民的日常生活和城市的健康发展。为了解市民对政务窗口服务的满意度&#xff0c;提升服务质量&#xff0c;某市委托民安智库专业市场调查公司开展了政务窗口服务满意度调查&…

若依框架部署到服务器刷新或者是退出登录出现404

登出错误404 改成/登出的时候重定向到根路径&#xff0c;让nginx匹配去找dist目录下的index.html文件 或者是直接改为/index.html&#xff0c;少一步可能会快一点&#xff1f; 不过会变得很丑,算了还是根目录吧 原版是index&#xff0c;那玩意是 针对路由的&#xff0c;而打包…

【重生之我要苦学C语言】 函数递归

函数递归 什么是递归&#xff1f; 递归就是函数自己调用自己 递归一定是基于函数的 在任何一次函数调用中&#xff0c;都会申请资源&#xff0c;申请的是内存中栈区的资源 栈区的资源是有限的&#xff0c;因此函数不能无限次的递归 递归的思想 把一个大型复杂问题层层转化为…

解决docker拉取readeck镜像报Error response from daemon: toomanyrequests问题

readeck 是一个内容中心&#xff0c;目前已支持中文翻译 这是本地化部署后的效果&#xff1a; 原命令为&#xff1a; docker run --rm -ti -p 8000:8000 -v readeck-data:/readeck codeberg.org/readeck/readeck:latest Unable to find image codeberg.org/readeck/readeck:la…

数据库的诗篇:深入探索 MySQL 表操作的艺术与哲学

文章目录 前言&#x1f338;一、创建表——搭建数据存储的基础框架1.1 基本语法1.2 创建表的实际案例解释&#xff1a; 1.3 表设计的最佳实践 &#x1f338;二、查看表结构——快速了解数据库设计2.1 使用 DESC 命令解释&#xff1a; 2.2 使用 SHOW COLUMNS 命令2.3 使用 SHOW …

[MySQL#2] 库 | 表 | 详解CRUD命令 | 字符集 | 校验规则

目录 一. 库操作 1. 创建数据库 2. 字符集和校验规则 校验规则对数据库的影响 显示创建数据库时对应的命令 3. 修改数据库 4. 数据库删除 备份和恢复 还原 查看连接情况 二. 表操作 1. 创建表&#xff08;定义实例化格式 2. 创建表案例 &#xff08;实例化数据类型…

【Fargo】12:参考mediasoup 重构 udpsocket管理、定时器、uv生命周期

udp 发送的包是动态分配的内存,即使考虑rtppacket类型,也是动态分配的内存构造rtppacket,因此应该考虑对这块内存管理起来。之前的实现采用了一个固定分配的内存,可能会节省内存,固定大小虽然不够灵活,但是可以规避频繁分配导致的内存碎片。每次都动态分配一个uv_udp_sen…

vue3+vite 部署npm 包

公司需要所以研究了一下怎么部署安装&#xff0c;比较简单 先下载个vue项目 不用安准路由&#xff0c;pinna 啥的&#xff0c;只需要一个最简单的模版 删掉App.vue 中的其它组件 npm create vuelatest 开始写自定义组件 新建一个el-text 组件, name是重点&#xff0c;vue3中…

【10天速通Navigation2】(三) :Cartographer建图算法配置:从仿真到实车,从原理到实现

前言 往期内容&#xff1a; 第一期&#xff1a;【10天速通Navigation2】(一) 框架总览和概念解释第二期&#xff1a;【10天速通Navigation2】(二) &#xff1a;ROS2gazebo阿克曼小车模型搭建-gazebo_ackermann_drive等插件的配置和说明 本教材将贯穿nav2的全部内容&#xff0c…

FreeSWITCH 简单图形化界面30 - 使用MYODBC时可能遇到的错误

FreeSWITCH 简单图形化界面30 - 使用MYODBC时可能遇到的错误 测试环境1、 MYODBC 3.51.18 or higher2、分析和解决2.1 解决1&#xff0c;降级MySQL ODBC2.2 解决2&#xff0c;修改FreeSWITCH代码 测试环境 http://myfs.f3322.net:8020/ 用户名&#xff1a;admin&#xff0c;密…

双11必买物品清单有哪些?双11推荐购买清单分享

随着双11购物节的到来&#xff0c;这个由光棍节演变而来的全民狂欢节已经成为了中国乃至全球最大的在线购物盛事。对于精明的购物者来说&#xff0c;双11不仅是一个购物的好时机&#xff0c;更是一个精打细算、囤货备需的黄金时刻。那么双11必买物品清单有哪些&#xff1f;在这…

基于Arduino的LED亮灭按键控制

一、项目简介 通过一个按键控制LED的亮灭&#xff0c;实现按键按一下LED点亮&#xff0c;再按一下LED熄灭&#xff0c;交替循环。 二、控制原理 1. 按键检测原理&#xff1a; 将Arduino的监测端口设置为输入上拉模式&#xff08;INPUT_PULLUP&#xff09;&#xff0c;用于连…

C++结合图形编程与物联网:你更偏向哪种方式来学习信息学奥赛?

随着信息学奥赛在全国范围内的热度逐年攀升&#xff0c;学生和家长们越来越重视如何有效备赛。传统的编程学习方式侧重于算法和数据结构&#xff0c;但随着科技的发展&#xff0c;图形化编程与物联网&#xff08;IoT&#xff09;项目逐渐成为新兴的学习路径。通过C结合图形化编…