详解数据结构之二叉树(堆)

news2024/11/26 9:41:37

详解数据结构之二叉树(堆)

树的概念

  • 树是一个非线性结构的数据结构,它是由 n(n>=0)个有限节点组成的一个具有层次关系的集合,它的外观形似一颗倒挂着的树,根朝上,叶朝下,所以称呼为树。每颗子树的根节点有且只有一个前驱,可以0个有多个后继。
    在这里插入图片描述

如图:这两颗树就不是树形结构,子树是不会相交的除根节点外每个节点有且只有一个父节点一颗有n个节点的树有n - 1条边。

在这里插入图片描述

  • 根节点

    • 根节点没有前驱节点,如图:9为根节点。
  • 父节点:若一个节点含有子节点,则称这个节点为字节的的父节点,如图9为12个父节点。

  • 子节点/孩子节点:一个节点含有父节点那这个节点就为孩子节点,如图12.

  • 节点的度:一个节点右多少个孩子节点那他的度就为多少。

    • 树的度,为最大节点的度,如上图,12这个节点的度为3,它是最大的,那他就为树的度。
  • 树的层次:从根开始定义为第一层,根节点的子节点为第二层,以此类推。

  • 树的深度、高度:树的最大层次,如图,树最大有4层,那树的深度为4。

  • 叶子节点:度为0的节点为叶子节点。

  • 分支节点:度不为0的节点。

  • 兄弟节点:具有相同父节点的节点互称为兄弟节点。

  • 节点的祖先:从根到该结点所经分⽀上的所有结点

    • 所有节点的祖先为根节点
  • 节点的子孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙

    • 除根节点外所有节点是根节点的子孙。
  • 森林:由m颗不相加的树的集合(m > 0)。

二叉树

在树形结构里最常用的就是二叉树,一颗二叉树是节点的有限集合,该集合右一个根节点加上两颗左子树和右字树组成或者为空。

二叉树形态:

  1. 空二叉树
  2. 只有一个根节点
  3. 根节点只有左子树
  4. 根节点只有右子树
  5. 根节点有左右子树

二叉树的特点:

  1. 二叉树不存在度大于2的节点
  2. 二叉树的节点有左右之分,次序不能颠倒,而二叉树又是有序树

特殊二叉树:

  • 满二叉树

满二叉树是节点总数满足 2^k - 1(k为树的最大层数)的树,每一层的节点个数都达到最大值。
在这里插入图片描述

  • 完全二叉树:

完全二叉树:对于一颗具有n个节点的二叉树按层序编号,如果编号为i(1 <= i <= n)的节点与同样深度的满二叉树中编号为i的节点在二叉树中的位置完全相同,这颗二叉树称为完全二叉树。

例如:

序号为2的节点没有右节点而序号为3的节点先有了序号为5、6的节点此时它们的序号与上图的完全二叉树序号并不相同,那它就不是完全二叉树。
在这里插入图片描述

正确的完全二叉树:
在这里插入图片描述

判断完全二叉树时,可以先按照当前二叉树的层次画一个或着心里想一个满二叉树,判断对应位置上的序号大小是否一致。

完全二叉树的性质:

  1. 叶子节点只能出现在最后两层
  2. 最下层的叶子节点一定集中在左部连续的位置
  3. 倒数第二层有叶子节点那他一定在右部连续位置

二叉树的性质

二叉树总节点个数:深度为k的二叉树具有,n = 2^k - 1个节点

二叉树第i层的节点个数:第i层的节点有,2^(i-1) 个。

对应具有n个节点个数的二叉树对应的深度为:k = logn + 1,底数为2

堆,将一个元素集合k里,所有数据按照完成二叉树的存储方式存储。

这里的堆是基于顺序存储结构实现,因为堆是一种要求严格的二叉树,在节点的顺序上,必须是从左孩子节点开始放数据,不会允许一个节点先有右孩子节点,而没有左孩子节点。

有了这种特殊的限制,在数组里就可以利用节点所对应的下标来寻找对于的父亲节点,孩子节点。‘

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

i > 0 ,i 位置的节点的双亲序号为:(i - 1) / 2,i = 0 为根节点为双亲

若 2i + 1 < n,左孩子序号:2i + 1,否则2i + 1>= n无左孩子

若 2i + 2 < n,右孩子序号:2i + 2,否则2i + 2>= n无右孩子

堆的分类:

  • 小根堆(小堆),父亲节点的大小总是小于孩子节点
    在这里插入图片描述

  • 大根堆(大堆),父亲节点的大小总是大于孩子节点

不满足以上两种的堆是无序的无效的堆,必须对其的循序进行调整。

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* arr;
	int size;
	int capacity;
}Heap;

功能实现

//初始化
void HpInit(Heap* hp);
//销毁
void HpDestory(Heap* hp);
//堆尾放数据
void HpPush(Heap* hp, HeapDataType x);
//出堆顶数据
void HpPop(Heap* hp);
//自下向上调整
void AdjustUp(HeapDataType* arr, int child);
//自上向下调整
void AdjustDown(HeapDataType* arr, int parent, int n);
//返回堆顶数据
HeapDataType HeapTop(Heap* hp);
//返回堆的数据个数
int HeapSize(Heap* hp)//交换函数Swap
void Swap(HeapDataType* x, HeapDataType* y);

初始化、销毁

初始化:基于顺序结构实现的堆,将指向堆的变量的地址传递过来使用一级指针接收,实现形参的改变影响到实参。初始化堆,只需对其指针置空、空间大小和栈顶置0即可。

void HpInit(Heap* hp)
{
	assert(hp);
     if(hp->arr)
         free(hp->arr);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

销毁堆:堆的空间是使用函数动态开辟的,那他得使用对应得free对空间进行释放,让后将堆的空间大小和,size置0即可。

需要注意的是若数组本身是空的那就不能对齐进行释放,在使用free释放前还需要使用if语句进行判断。是否为空。

void HpDestory(Heap* hp)
{
	assert(hp);
	if (hp->arr)
		free(hp->arr);
	hp->capacity = hp->size = 0;
}

堆插入数据、自下向上调整算法

在堆里插入数据有着严格的要求,必须按照这从左节点到右节点的顺序放入数据,不能跳过左节点,先放入右节点的数据。
在这里插入图片描述

现在想要在堆里放入一个数据,不能是我想在哪放就在哪放的,必须先将节点20的右孩子节点放入数据,才能到下一层放入数据。这些严格的要求也导致了用数组实现堆,存放数据也是从末尾开始。

而前文说过,堆是按照一定顺序存放的,在如图的小堆里,我放入了一个10的数据,改如何应对呢?这里就得使用到堆的自下向上调整函数,其函数功能是将堆里新放入的数据按照小堆的结构摆放。

堆插入数据

在堆里放入数据只需在数组末尾存放即可。

需要注意的是,对空间大小的判断,以及自下向上调整算法。

在函数里,需要对有效数据个数和空间大小是否相等进行判断,所以使用if语句,在if语句里,使用realloc函数开辟,为啥不使用malloc函数呢~,我们需要实现的动态开辟,存放的数据越多,空间越开越大,malloc函数做不到。扩容时需扩容原空间的2倍或3倍可以减少扩容操作的频率。如果每次只增加少量空间,那么在元素数量增长时,需要频繁进行扩容操作,这会降低性能。

在完成对空间大小的判断与开辟后,将待插入数据放入数组末尾即可。然后使用自下向上调整算法对新放入的数据根据大小调整顺序。

void HpPush(Heap* hp, HeapDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		int newcapacity = hp->arr == 0 ? 4 : hp->capacity * 2;
		HeapDataType* newarr = (HeapDataType*)realloc(hp->arr, sizeof(HeapDataType) * newcapacity);
		if (newarr == NULL)
		{
			perror("malloc ");
			exit(1);
		}
		hp->arr = newarr;
		hp->capacity = newcapacity;
	}
	hp->arr[hp->size] = x;
	AdjustUp(hp->arr, hp->size);
	hp->size++;//交换完在进行加加,提前加加hp->arr[hp->size++] = x;然后在调整会有越界情况
}

自下向上调整算法

这里将交换两个数据的功能封装为一个函数,后续实现出堆顶数据还需要使用它。

void Swap(HeapDataType* x, HeapDataType* y)
{
	HeapDataType temp = *x;
	*x = *y;
	*y = temp;
}

计算父亲节点的大小:父亲 = (孩子 - 1) / 2

自下向上算法的实现基于二叉树的性质,更具孩子节点找父亲节点。新插入的孩子节点需要与父亲节点进行比较大小,若孩子节点小于父亲节点就交换顺序,而新的父亲节点又是前一个节点的孩子节点,同样需要判断其大小,然后交换顺序,若父亲节点小于孩子节点的话就不需要继续循环下去使用break跳出。

循环结束的条件:当child不大于0时跳出,若child都小于0了还怎么访问堆里的数据,而child刚好为0的说明此时孩子指的是根节点,根节点并不需要交换,所以child不大于0时跳出即可。

从孩子开始为堆的最后一个数据,所以称为自下向上调整。


void AdjustUp(HeapDataType* arr, int child)//传递数组和数组的大小,也就是插入数据的位置
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
        //小堆:<
        //大堆:>
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);//交换
			child = parent;//交换完后更新孩子节点的位置。
			parent = (child - 1) / 2;//跟新父亲节点的位置,为新孩子节点的父亲节点。
		}
		else
		{
			break;
		}
	}
}
建大堆

若我们需要一个大堆,这里只需要改变if语句里的条件判断,将找更小的逻辑改为找更大的数据即可。

出堆顶数据、自上向下调整算法

出堆顶数据也有严格的要求,并不是将数组末尾的数据删除,直接将size减1,也不是让数组所有数据前移一位,这将会到的堆的顺序全部乱套了~坏掉了!

出堆顶数据

出堆顶数据是将堆顶的数据与堆尾的数据进行交换,然后让size减1(堆的数据个数),最后将新的堆顶数据按照小堆的摆放调整顺序。

出堆顶数据时需要注意,堆不能为空,以及数组也不能为空否则将会做坏事,导致统子哥报错。

void HpPop(Heap* hp)
{
	assert(hp && hp->size);
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);//将最后一个元素与堆顶元素交换
	hp->size--;
	//调整顺序
	AdjustDown(hp->arr, 0, hp->size);
}

自上向下调整算法

左孩子:孩子 = 父亲 * 2 + 1,child < n

右孩子:孩子 = 父亲 * 2 + 2,child < n

前文提过的自下向上调整算法是向上找父亲节点,而自上向下算法是向下找孩子节点更具传递过来的参数 parent 向下个找孩子。

与前一个调整算法相比,它会进行更多的判断,因为向下找的孩子有两个。而我们默认的孩子起始是左孩子节点。

在对孩子节点与父亲节点比较大小交换之前还需要比较左孩子节点和右孩子节点,arr[child] > arr[child + 1],若左孩子节点大于有孩子,那child就需要加1,但还有个前提,万一child加1后刚好不满足 child < n的条件从而在后续的交换里导致数组越界访问,所以在if语句里还需要加上一条判断 child + 1 < n

执行孩子节点与父亲节点交换的if语句里,在交换完两个节点后需要更新新的父亲节点,和孩子节点来是否存在比父亲节点还小的值。最后若孩子节点大于父亲节点,那就说明不需要交换,使用break跳出循环即可。

void AdjustDown(HeapDataType* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
                               //小堆:>
        					   //大堆:<
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
        //小堆:<
        //大堆:>
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
建大堆

若我们需要一个大堆,那该如何调整,这里只需要改变其两个if语句里的条件判断,将找更小的逻辑改为找更大的数据即可。

返回堆顶数据

将数组下标为零的元素返回,即是堆顶。

HeapDataType HeapTop(Heap* hp)
{
	assert(hp && hp->size);
	return hp->arr[0];
}

返回堆的数据个数

将size的大小返回即是堆存储的数据个数。

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

源码

Heap.h

#pragma once

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

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* arr;
	int size;
	int capacity;
}Heap;



//初始化
void HpInit(Heap* hp);

//销毁
void HpDestory(Heap* hp);

//堆尾放数据
void HpPush(Heap* hp, HeapDataType x);
//出堆顶数据
void HpPop(Heap* hp);
//自下向上调整
void AdjustUp(HeapDataType* arr, int child);
//自上向下调整
void AdjustDown(HeapDataType* arr, int parent, int n);
//返回堆顶数据
HeapDataType HeapTop(Heap* hp);
//返回堆的数据个数
int HeapSize(Heap* hp)//交换函数Swap
void Swap(HeapDataType* x, HeapDataType* y);

Heap.c

#define _CRT_SECURE_NO_WARNINGS
#include "Heap.h"

//初始化
void HpInit(Heap* hp)
{
	assert(hp);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

//销毁
void HpDestory(Heap* hp)
{
	assert(hp);
	if (hp->arr)
		free(hp->arr);
	hp->capacity = hp->size = 0;
}
void Swap(HeapDataType* x, HeapDataType* y)
{
	HeapDataType temp = *x;
	*x = *y;
	*y = temp;
}
//自下向上调整
void AdjustUp(HeapDataType* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//堆尾放数据
void HpPush(Heap* hp, HeapDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		int newcapacity = hp->arr == 0 ? 4 : hp->capacity * 2;
		HeapDataType* newarr = (HeapDataType*)realloc(hp->arr, sizeof(HeapDataType) * newcapacity);
		if (newarr == NULL)
		{
			perror("malloc ");
			exit(1);
		}
		hp->arr = newarr;
		hp->capacity = newcapacity;
	}
	hp->arr[hp->size] = x;
	AdjustUp(hp->arr, hp->size);
	hp->size++;
}

//自上向下调整
void AdjustDown(HeapDataType* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//出堆顶数据
void HpPop(Heap* hp)
{
	assert(hp && hp->size);
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);//将最后一个元素与堆顶元素交换
	hp->size--;
	//调整顺序
	AdjustDown(hp->arr, 0, hp->size);
}
//返回堆顶数据
HeapDataType HeapTop(Heap* hp)
{
	assert(hp && hp->size);
	return hp->arr[0];
}

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}


		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//出堆顶数据
void HpPop(Heap* hp)
{
	assert(hp && hp->size);
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);//将最后一个元素与堆顶元素交换
	hp->size--;
	//调整顺序
	AdjustDown(hp->arr, 0, hp->size);
}
//返回堆顶数据
HeapDataType HeapTop(Heap* hp)
{
	assert(hp && hp->size);
	return hp->arr[0];
}

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

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

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

相关文章

7. 聚类算法 KMeans

聚类算法 KMeans 1. 应用&#xff1a;大数据杀熟2. 迭代法3. 代码 1. 应用&#xff1a;大数据杀熟 618、双十一&#xff0c;平台要对用户进行分类&#xff1a;用户&#xff1a; 脑残粉&#xff08;不降价&#xff0c;或者涨点价&#xff09;墙头草&#xff08;给点小优惠券&am…

二叉树基础及实现(一)

目录&#xff1a; 一. 树的基本概念 二. 二叉树概念及特性 三. 二叉树的基本操作 一. 树的基本概念&#xff1a; 1 概念 &#xff1a; 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因…

数据结构之初始二叉树(4)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 二叉树的基本操作 二叉树的相关刷题&#xff08;上&#xff09;通过上篇文章的学习&#xff0c;我们…

基于密钥的身份验证(Linux-Linux)

A主机&#xff1a; 1、生成密钥对 [rootservera ~]# ssh-keygen查看公钥 注&#xff1a;id_rsa为私钥&#xff08;证书&#xff09;&#xff0c;id_rsa.pub为公钥 2、注册公钥到服务器 [rootservera ~]# ssh-copy-id root172.25.250.106 查看.ssh 3、使用密钥连接服务器 #…

ViT(Vision Transformer)网络结构详解

本文在transformer的基础上对ViT进行讲解&#xff0c;transformer相关部分可以看我另一篇博客&#xff08;transformer中对于QKV的个人理解-CSDN博客&#xff09;。 一、网络结构概览 上图展示了Vision Transformer (ViT) 的基本架构&#xff0c;我按照运行顺序分为三个板块进…

配置web服务器

当访问网站www.haha.com时显示&#xff1a;haha&#xff1b;当访问网站www.xixi.com/secret/显示&#xff1a;this is secret 第一步&#xff0c;配置一个新的IP 确认后 esc返回 第二步&#xff1a;重启ens160 第三步&#xff1a;创建目录&#xff0c;并且在文件内写入内容 第…

英福康INFICON UL1000检漏仪介绍PPT

英福康INFICON UL1000检漏仪介绍PPT

【周记】2024暑期集训第二周(未完待续)

文章目录 日常刷题记录合并果子题目解析算法思路代码实现 中位数题目解析算法思路代码实现 C学习笔记队列queue双端队列 deque优先队列 priority_queue定义常见操作 upper_bound 日常刷题记录 合并果子 题目解析 有一堆果子&#xff0c;每次可以将两小堆合并&#xff0c;耗费…

verilog行为建模(四):过程赋值

目录 1.两类过程赋值2.阻塞与非阻塞赋值语句行为差别举例13.阻塞与非阻塞赋值语句行为差别举例24.阻塞与非阻塞赋值语句行为差别举例35.举例4&#xff1a;非阻塞赋值语句中延时在左边和右边的差别 微信公众号获取更多FPGA相关源码&#xff1a; 1.两类过程赋值 阻塞过程赋值执…

漫威争锋Marvel Rivals测试搜不到 漫威争锋Marvel Rivals怎么搜

漫威争锋&#xff0c;一款今年即将上线的6v6的fps游戏&#xff0c;漫威争锋Marvel Rivals一经公布就吸引了广大玩家的兴趣。玩家将在游戏中扮演一名名经典且有趣的漫威英雄&#xff0c;与敌人展开对决。而且该游戏中有着很多的漫威英雄供我们挑选使用&#xff0c;有着很多英雄的…

【数据结构】排序算法——Lessen1

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

音乐播放器的优雅之选,黑金ONIX Overture XM5,更悦耳的音质体验

如今想要随时沉浸式的体验高品质的数字音乐资源&#xff0c;一款简单好用的音乐播放器必不可少&#xff0c;多年来在音乐爱好者的心中的经典品牌屈指可数&#xff0c;英国品牌ONIX算是一个&#xff0c;其Horizon系列以优雅的设计以及出众的品质&#xff0c;收获了很多忠实粉丝。…

OpenAI发布迷你AI模型GPT-4o mini

本心、输入输出、结果 文章目录 OpenAI发布迷你AI模型GPT-4o mini前言OpenAI发布迷你AI模型GPT-4o mini英伟达联合发布 Mistral-NeMo AI 模型:120 亿参数、上下文窗口 12.8 万个 tokenOpenAI发布迷你AI模型GPT-4o mini 编辑 | 简简单单 Online zuozuo 地址 | https://blog.csd…

【ADRC笔记】LESO-Wb

公式推导(bilibili) 一阶ESO 二阶ESO 二阶自抗扰控制器基本原理 选取状态变量 观测器收敛性推导 wo 观测器带宽

C语言·函数(超详细系列·全面总结)

前言&#xff1a;Hello大家好&#x1f618;&#xff0c;我是心跳sy&#xff0c;为了更好地形成一个学习c语言的体系&#xff0c;最近将会更新关于c语言语法基础的知识&#xff0c;今天更新一下函数的知识点&#xff0c;我们一起来看看吧&#xff01; 目录 一、函数是什么 &a…

HTTPServer改进思路1

Nginx源码思考项目改进 架构模式 事件驱动架构(EDA&#xff09;用于处理大量并发连接和IO操作 优点&#xff1a;高效处理大量并发请求&#xff0c;减少线程切换和阻塞调用技术实现&#xff1a;直接使用EPOLL&#xff0c;参考Node.js的http服务器 网络通信 协议&#xff1a;HTT…

day6 io线程

获取终端输入的字符

记录 cocos 开发问题 ,微信 wx.xxx函数 报找不到名称“wx”

今天写微信排行榜遇到 问题分享一下。 目前&#xff0c;微信、百度 和 抖音 小游戏这些平台为了保护其社交关系链数据&#xff0c;增加了 开放数据域 的概念&#xff0c;这是一个单独的游戏执行环境。开放数据域中的资源、引擎、程序&#xff0c;都和主游戏&#xff08;主域&a…

【2】Spring Cloud 工程搭建

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring Cloud实战&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.声明项目依赖和项目构建插件 2.完善子项目订单服务 2.1完善启动…

[Spring] Spring配置文件

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…