堆与二叉树(上)

news2024/12/24 10:48:09

本篇主要讲的是一些概念,推论和堆的实现(核心在堆的实现这一块)

涉及到的一些结论,证明放到最后,可以选择跳过,知识点过多,当复习一用差不多,如果是刚学这一块的,建议打勾的概念多留意,推论前三个相似,了解其中一个即可,重点看推论四(主要是和堆的实现有关),一定要动手实现堆排序哦,希望对你们有帮助


讲堆之前我们先介绍一下 树 的概念

一 . 树的概念

什么是树?

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。它的结构很像一棵倒过来的树

正因为这一点,有些结构的名称就可以跟数联系起来

  • 其中一个特殊的节点叫作根节点,它没有前驱结点
  • 除根节点以外,其余若干个集合叫作子树(子树之间不能相交)
  • 除根节点以外,每个父节点都有一个前驱结点

错误的图示

树的相关知识

图例:

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6

叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点 (注:A不仅是根节点,也是父节点)

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

二.  二叉树

二叉树的概念

二叉树是一种特殊的树

每个父节点都最多有两个子节点

图例1

特殊的二叉树

  1. 满二叉树

       要求每一个父节点必须有两个子节点(如上述图例1

     2. 完全二叉树

      要求节点是连续存放的

(这里用数组的存储形式来说明)

正确示范:

错误示范:

二叉树的一些推论

规定根节点的层数为 1 ,总层数为 h ,节点个数为 n

      推论1

      已知总层数为 h,求最后一层的节点个数的最大值:

      2 ^(h - 1)

      推论2

       已知节点个数为 n,求具有n个结点的满二叉树的深度 h

     

      推论3

     若根节点层数为1(保证不是空树),已知深度为 h ,则求二叉树的最大节点数:

       推论4

         已经节点总个数 n 的完全二叉树 ,从第一个根节点开始以 0 编号,从左到右,从上到下编号依次增加 1

        求 父节点 和 左右孩子节点 的关系(parent , child 1 和 child2 都是下标)

        a. 若知道 parent:

       则 child1 = parent * 2 + 1

      child2 = parent * 2 + 2

       b. 若知道 child (无论哪个孩子下标都可以):

      则 parent = (child - 1) / 2

推论5

对任何一棵二叉树, 如果度为0其叶结点个数为 n0 , 度为2的分支结点个数为 n2 ,

则有 n0 = n2 +1

证明推论5

用图例阐明遇到的情况:

我们发现当 n1 + 1 ,意味着 n0 - 1

n2 + 1 , 意味着 n1 - 1


证明推论1

证明推论2

 证明推论3

是推论2

(即是满二叉树)

 证明推论4

完全二叉树有一个性质,它是连续存放的

第一个结论通过画图我们很容易就得到了

第二个结论主要注意的是:

由于下标都是 int 类型的,最终得到的一定是整数

证明推论5

用图例阐明遇到的情况:

我们发现当 n1 + 1 ,意味着 n0 - 1

n2 + 1 , 意味着 n1 - 1

三 . 二叉树的储存结构

二叉树有两种储存形式:

一个是 顺序储存结构 , 另一个是 链式储存结构

  1. 顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树就会有空间的浪费

图例

      2. 二叉树的链式存储结构是指,用链表来表示一棵二叉树,

     一般两个指针存放左右孩子的节点

图例

四 . 堆的概念

堆的性质

堆满足以下性质:

  1. 是一棵 完全二叉树
  2. 只有 小堆 或者 大堆

(小堆:父节点的值永远小于等于两个孩子节点的值;

大堆:父节点的值永远大于等于两个孩子节点的值)

图例

堆的实现

1. 堆的排序

堆的排序有两种:向上排序法 和 向下排序法(这里用顺序结构)

向上排序法

该方法可以在一个个把数值放进去的同时进行排序

实现思路如下:

父节点下标从 0 开始,一个节点也是一个堆(所以放入第一个数的时候是不用进行排序的),后面的数先把值放入数组,再进行与父节点进行对比(已知 child 下标,求 parent 下标,回到推论4

[由于可能发生多次交换问题,这里需要用到循环]

如果 父节点 的值 大于 子节点 的值(排小堆),则交换;

如果 父节点 的值 小于 子节点 的值(排大堆),则交换.

并且 此时 child 下标 和 parent 下标都要发生改变,让上一个父节点和上一个子节点作比较

注意:想要改变两个值,一定要传地址

结束条件:

如果不用交换可以跳出循环

或者到 child > 0 时,跳出循环


代码

void swap(int* p1, int* p2)
{
	if (*p1 == *p2)
	{
		return;
	}
	else
	{
		int tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
	}
}
void upAdjust(int* pa, int n)
{
	for (int i = 1; i < n; i++)
	{
		int child = i;
		int parent = (child - 1) / 2;
		while (child > 0)
		{
			if (pa[parent] > pa[child])
			{
				swap(&pa[parent], &pa[child]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}

}

向下排序法

给一组无序数组,我们给它排序,这里需要我们从最后一棵子树(度不为0)的父节点和它的子节点开始倒着排序

像这样:

我们以第一棵树为例(序号为1的树)

这里更容易找到子节点 , 然后根据公式推出父节点

但是我们的思路是 让父节点与 (更小的)子节点比较(小堆),或者与(更大的)子节点比较 (大堆)

这里的子节点需要通过比较确定,所以我们先去找父节点的下标起

parent = (n - 1) / 2

child = parent * 2 + 1(假设第一个节点就是我们需要比较的那个节点)

再与第二个节点进行对比,确定 child 下标(判断时为了防止没有第二个子节点而越界的情况,一定要对其进行判断)

对比 父节点 和 子节点 的值

我们还需要向下调整

此时 parent = child

child = parent * 2 + 1

一直到 child >= n (这是最内层的循环结束条件)

比完第一棵树,再比第二棵树

此时 parent -= 1 (完全二叉树是连续的)

注意:由于可能发生连续向下的调整,导致 parent 的值会发生改变,所以我们需要得到第一棵树最开始的 parent

child = parent * 2 + 1

重复上述动作,直到

parent >= 0(这是最外层的循环条件)


代码

void swap(int* p1, int* p2)
{
	if (*p1 == *p2)
	{
		return;
	}
	else
	{
		int tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
	}
}
void downAdjust(int* pa, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && pa[child] > pa[child + 1])
		{
			child++;
		}
		if (pa[parent] > pa[child])
		{
			swap(&pa[parent], &pa[child]);
		}
		parent = child;
		child = parent * 2 + 1;
        else
        {
	        break;
        }
	}
}
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	downAdjust(arr,i,n);
}

2. 实现堆

堆的实现分几步:(这里用顺序结构)

堆的步骤

a. 构造一个堆的结构体

结构体成员:

存放整型数组的指针 : int *a

整个数组的元素个数 : int size

整个数组的的容量 : int capacity

b. 初始化结构体

size = 0

让 a 动态开辟 4 * sizeof(int) 个字节

capacity = 4

c. 放入元素

创建一个自定义函数放入元素

注意:

若 size ==capacity,开辟 sizeof( int ) * capacity * 2 的字节

capacity = capacity * 2

先一个个从插入元素,再进行调整(这里我们用向上调整法)

size++

d. 判断是否为空

创建一个自定义函数,判断数组里面是否还有元素很简单,只要判断

size == 0 即可

e. 删除元素(这里我们的删除一定是删除根节点的值)

创建一个自定义函数删除元素

删除元素第一步是一定要判断是否为空

为了继续维护堆,我们需要一个元素覆盖根节点的值,从而抹去这个值,但是如果根节点与它的子节点直接进行覆盖,会破坏之前的大堆(或 小堆)

所以我们的办法是:

把根节点的值与最后一个值交换(保证其它的父子关系不变)

size--

我们再使用 向下调整法

f. 打印堆

g. 释放堆的空间


代码

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct HeapList
{
	int* a;
	int size;
	int capacity;
}HP;
void InitHeap(HP *heap)
{
	int* pa = (int*)malloc(sizeof(int) *  4);
	if (pa == NULL)
	{
		perror("realloc");
		return;
	}
	heap->a = pa;
	heap->capacity = 4;
	heap->size = 0;
}
void swap(int* p1, int* p2)
{
	if (*p1 == *p2)
	{
		return;
	}
	else
	{
		int tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
	}
}
void upAdjust(int* pa, int n)
{
	for (int i = 1; i < n; i++)
	{
		int child = i;
		int parent = (child - 1) / 2;
		while (child > 0)
		{
			if (pa[parent] > pa[child])
			{
				swap(&pa[parent], &pa[child]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}

}
void downAdjust(int* pa, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && pa[child] > pa[child + 1])
		{
			child++;
		}
		if (pa[parent] > pa[child])
		{
			swap(&pa[parent], &pa[child]);
		}
		parent = child;
		child = parent * 2 + 1;
	}
}
void HeapPush(HP* heap ,int x)
{
	if (heap->size == heap->capacity)
	{
		int* pa = (int*)realloc(heap->a, sizeof(int) * heap->capacity * 2);
		if (pa == NULL)
		{
			perror("realloc");
			return;
		}
		heap->a = pa;
		heap->capacity = heap->capacity * 2;
	}
	heap->a[heap->size] = x;
	heap->size++;
	upAdjust(heap->a, heap->size);
}
bool IsEmpty(HP* heap)
{
	return heap->size == 0;
}
void HeapPop(HP* heap)
{
	assert(!IsEmpty(heap));
	swap(&heap->a[0], &heap->a[heap->size - 1]);
	heap->size--;
	downAdjust(heap->a, 0, heap->size);
}
void PrintHeap(HP* heap)
{
	for (int i = 0; i < heap->size; i++)
	{
		printf("%d ", heap->a[i]);
	}
	printf("\n");
}
void DestoryHeap(HP* heap)
{
	free(heap->a);
	heap->a = NULL;
	heap->size = heap->capacity = 0;
}

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

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

相关文章

python实现贪吃蛇游戏

文章目录 1、项目说明2、项目预览3、开发必备4、贪吃蛇代码实现4.1、窗口和基本参数实现4.2、绘制背景4.3、绘制墙壁4.4、绘制贪吃蛇4.5、绘制食物4.6、实现长度信息显示4.7、定义游戏暂停界面4.8、定义贪吃蛇死亡界面4.9、实现贪吃蛇碰撞效果4.10、实现添加食物功能4.11、实现…

怎么检测DC-DC电源模块稳定性?电源测试系统测试有什么优势?

DC-DC电源模块稳定性测试 稳定性是衡量DC电源模块的重要指标&#xff0c;电源模块的稳定性直接影响着电源产品和设备的工作稳定性。DC-DC电源模块的稳定性&#xff0c;可以通过检测输出电压、输出电流、负载、波形、效率等参数来评估。 1. 静态测试方法 静态测试是通过直流电压…

理解SwiftUI中的matchedGeometryEffect

matchedGeometryEffect SwiftUI 2.0 引入了一个新的修饰符&#xff1a;matchedGeometryEffect 。使用matchedGeometryEffect可以为整个层次结构中的视图创建过渡动画。只需要将它附加到要进行动画处理的两个视图上&#xff0c;并给它们指定相同的标识符。有了 matchedGeometry…

深耕元宇宙领域,强势发力文旅市场

2023年12月14日&#xff0c;“承上启下 智元宇宙&#xff1a;2024元宇宙与人工智能应用场景闭门会——苏州”在苏州泰山路2号百度VR&#xff08;苏州&#xff09;赋能中心成功举办。会议邀请了苏州本地的相关优秀企业代表、科创精英、投资与行业技术代表、公司创始人共计约50余…

龙迅LT6211B,HDMI1.4转LVDS,应用于AR/VR市场

产品描述 LT6211B 是一款用于 VR/ 显示应用的高性能 HDMI1.4 至 LVDS 芯片。 对于 LVDS 输出&#xff0c;LT6211B 可配置为单端口、双端口或四端口。对于2D视频流&#xff0c;同一视频流可以映射到两个单独的面板&#xff0c;对于3D视频格式&#xff0c;左侧数据可以发送到一个…

MySQL表的增删改查(初阶)

CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写。且增删改查&#xff08;CRUD&#xff0c;create&#xff0c;retrieve&#xff0c;update&#xff0c;delete&#xff09;数据库的核心模块。 1. 新增&#xff08;Create&#xff09; 实…

去面试性能测试工程师必问的问题,

性能测试的三个核心原理是什么&#xff1f; 1.基于协议。性能测试的对象是网络分布式架构的软件&#xff0c;而网络分布式架构的核心是网络协议 2.多线程。人的大脑是单线程的&#xff0c;电脑的cpu是多线程的。性能测试就是利用多线程的技术模拟多用户去负载 3.模拟真实场景。…

(详解版)创建线程的四种方式

文章目录 Java中创建线程的四种方式1. 继承Thread类并重写 run 方法来创建线程2. 实现Runnable接口并实现 run 方法来创建线程。3. 使用Callable接口创建线程4. 使用Executor框架创建线程 Java中创建线程的四种方式 接下来我会详细解释这四种方式创建线程如何实现. 我们如果要…

STM32——串口通信应用篇

一、引言 STM32微控制器是一款功能强大的嵌入式系统芯片&#xff0c;广泛应用于各种领域。其中&#xff0c;串口通信是其重要功能之一&#xff0c;可用于与外部设备进行数据交换和控制。本文将介绍STM32串口通信的基本原理、应用场景以及实现方法。 二、STM32串口通信基本原理 …

linux xxd命令(将文件或标准输入转换为hex(十六进制)和ASCII(美国信息交换标准代码)表示,或者从hex dump(十六进制转储)反向到二进制)

文章目录 Linux xxd命令安装xxd基本使用方法创建hex dump从hex dump恢复到二进制 命令选项疑难技术点解析在脚本中使用xxd从hex dump恢复数据 总结 Linux xxd命令 xxd是一个在Linux和UNIX系统中常用的工具&#xff0c;主要用于将文件或标准输入转换为hex&#xff08;十六进制&…

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

[Linux] LVS负载均衡群集——DR模式

一、 DR模式的特点 直接路由&#xff1a; 在LVS_DR模式下&#xff0c;负载均衡器不修改数据包的IP地址&#xff0c;只修改目的MAC地址。这使得数据包可以直接路由到后端实际服务器上&#xff0c;而不需要返回到负载均衡器。 高性能&#xff1a; 由于数据包在传输过程中不需要回…

dubbo--03--- dubbo 支持的9种协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Dubbo框架特性Dubbo 和 Spring Cloud区别 dubbo 支持的9种协议协议类型1、dubbo 协议 (默认)特性配置常见问题 2、rmi 协议3、hessian 协议4、http 协议特性 5、web…

案例073:基于微信小程序的智慧旅游平台开发

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【MYSQL】-表的操作

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

functools.partial:Python中灵活函数部分应用的工具

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;functools.partial是一个强大的工具&#xff0c;它提供了一种部分应用函数的方式&#xff0c;能够在创建新函数时固定部分参数&#xff0c;从而在后续调用中减少需要传递的参数数量。…

python中random.seed()和random.getstate()用法详解

python中random.seed()和random.getstate()用法详解 摘要 python的random包经常被用于模拟实验的重现&#xff0c;数据集的随机划分的确定性重现。然而&#xff0c;我本人之前对random.seed()什么时候调用&#xff0c;调用之后会对之后多少代码起决定性作用这一块感到云里雾里…

压测方案设计..

01 为什么要做压测 1、什么是压力测试&#xff1f; 不断向被测对象施加压力&#xff0c;测试系统在压力情况下的表现。 2、压力测试的目的是什么&#xff1f; 测试得出系统的极限性能指标&#xff0c;从而给出合理的承诺值或者容量告警&#xff1b; 找出系统的性能瓶颈&am…

清华提出ViLa,揭秘 GPT-4V 在机器人视觉规划中的潜力

人类在面对简洁的语言指令时&#xff0c;可以根据上下文进行一连串的操作。对于“拿一罐可乐”的指令&#xff0c;若可乐近在眼前&#xff0c;下意识的反应会是迅速去拿&#xff1b;而当没看到可乐时&#xff0c;人们会主动去冰箱或储物柜中寻找。这种自适应的能力源于对场景的…

51单片机简易出租车计费系统仿真设计

51单片机简易出租车计费系统仿真设计( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus 8.9及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0036 1.主要功能&#xff1a; 出租车计费系统设计内容&#xff1a; 1、…