【数据结构初阶】顺序结构二叉树(堆)接口实现超详解

文章目录

  • 1.树
    • 1. 1 树的概念与结构
    • 1. 2 树的相关术语
    • 1. 3 树的表示
    • 1. 4 树形结构实际运用场景
  • 2.二叉树
    • 2. 1 概念与结构
    • 2. 2 特殊的二叉树
      • 2. 2. 1 满二叉树
      • 2. 2. 2 完全二叉树
    • 2. 3 二叉树存储结构
      • 2. 3. 1 顺序结构
      • 2. 3. 2 链式结构
  • 3. 实现顺序结构二叉树(小堆)
    • 3. 1 堆的概念与结构
    • 3. 2 堆的实现
      • 3. 2. 1 堆的定义
      • 3. 2. 2 初始化与销毁
      • 3. 2. 3 交换
      • 3. 2. 4 入堆与向上调整函数
      • 3. 2. 5 判空与取堆顶数据与堆的大小
      • 3. 2. 6 出堆与向下调整
      • 3. 2. 7 大堆


1.树

1. 1 树的概念与结构

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。有一个特殊的结点,称为根结点根结点没有前驱结点
除根结点外,其余结点被分成 M(M>0)个互不相交的集合 T1、T2、…、Tm ,其中每一个集合Ti (1 <= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。
树形结构中,子树之间不能有交集,否则就不是树形结构。其实就是一个节点不能有多个前驱
树可以看做是这样的:
树
是现实中的树倒过来的样子。

这里有一些不是树的结构存储作为对比
1

  1. 子树是不相交的(如果存在相交就是图了,图是另一种数据结构)
  2. 除了根结点外,每个结点有且仅有一个父结点
  3. 一棵N个结点的树有N-1条边

1. 2 树的相关术语

以此图为例:
1

  1. 父结点 / 双亲结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
  2. 子结点 / 孩子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
  3. 结点的度:一个结点有几个孩子,他的度就是多少; 比如A的度为 6,F的度为 2,K的度为 0
  4. 树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为 6
  5. 叶子结点 / 终端结点:度为 0 的结点称为叶结点;如上图:B、C、H、I…等结点为叶结点
  6. 分支结点 / 非终端结点:度不为 0 的结点; 如上图: D、E、F、G…等结点为分支结点
  7. 兄弟结点 : 具有相同父结点的结点互称为兄弟结点(亲兄弟); 如上图:B、C是兄弟结点
  8. 结点的层次 : 从根开始定义起,根为第 1 层,根的子结点为第 2 层,以此类推。
  9. 树的高度或深度:树中结点的最大层次; 如上图:树的高度为 4
  10. 结点的祖先 : 从根到该结点所经分支上的所有结点; 如上图:A是所有结点的祖先
  11. 路径 : 一条从树中任意节点出发,沿父节点 - 子节点连接,达到任意节点的序列; 比如A到Q的路径为:A - E - J - Q; H到O的路径H - D - A - E - J - Q
  12. 子孙 : 以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
  13. 森林 : 由m(m > 0)棵互不相交的树的集合称为森林

1. 3 树的表示

孩子兄弟表示法
树结构相对线性表就比较复杂了,要存储表示起来也比较麻烦,既然保存数据,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。这里就先介绍其中最常用的孩子兄弟表示法

struct TreeNode{struct Node* child; 	// 最左边的孩子结点struct Node* brother; 	// 指向其右边的下一个兄弟结点int data; 				// 结点中的数据域
};

1
以这个二叉树为例,假如想从A找到I,就是:

A->child->child->brother->brother->child->brother

通过这样的存储方式,我们可以找到树上的每一个节点。

1. 4 树形结构实际运用场景

文件系统是计算机存储和管理文件的一种方式,它利用树形结构来组织和管理文件和文件夹。在文件系统中,树结构被广泛应用,它通过父结点和子结点之间的关系来表示不同层级的文件和文件夹之间的关联。
1

2.二叉树

2. 1 概念与结构

在树形结构中,我们最常用的就是二叉树,一棵二叉树是结点的一个有限集合,该集合由一个根结点加上两棵别称为左子树和右子树的二又树组成或者为空
2
从上图可以看出二叉树具备以下特点:

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

注意:对于任意的二又树都是由以下几种情况复合而成的
3

2. 2 特殊的二叉树

2. 2. 1 满二叉树

一个二叉树,如果每一个层的结点数都达到最大值或为0,则这个二叉树就是满二叉树
也就是说,如果一个二叉树的层数为 K,且结点总数是2k-1,它就是满二叉树。

2

2. 2. 2 完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。
对于深度为K,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时(简单地讲,就是第X层没有完全放满时,X+1层不能有节点,且没有满的层的节点必须是从左往右排布的)称之为完全二叉树。
要注意的是满二叉树是一种特殊的完全二叉树

2
二叉树性质
根据满二叉树的特点可知:

  1. 若规定根结点的层数为 1,则一棵非空二叉树的第i层上最多有 2i-1 个结点
  2. 若规定根结点的层数为 1,则深度为 h 的二叉树的最大结点数是 2h-1
  3. 若规定根结点的层数为 1,具有 n 个结点的满二叉树的深度h = log2(n+1)(log以2为底,n+1为对数)

2. 3 二叉树存储结构

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

2. 3. 1 顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二又树更适合使用顺序结构存储
1
通常把(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

2. 3. 2 链式结构

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链三叉链,现在我们接触的一般是二叉链。
高阶数据结构如红黑树等会用到三叉链。
2
3

3. 实现顺序结构二叉树(小堆)

一般堆使用顺序结构的数组来存储数据,堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他的特性。

3. 1 堆的概念与结构

如果有一个关键码的集合K = {k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储,在一个一维数组中,并满足:

Ki <= K2*i+1 (或Ki >= K2*i+1 且 Ki <= K2*i+2)

则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆
4
堆具有以下性质:

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

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

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

3. 2 堆的实现

我们先以小堆为例进行实现:

3. 2. 1 堆的定义

//小堆
typedef int HPDataType;
typedef struct Heap
{HPDataType* arr;int size;int capacity;
}Heap;

其实可以看到,堆的底层结构和动态顺序表是一模一样的,所以部分接口和顺序表也是一样的,就简略介绍了。

3. 2. 2 初始化与销毁

创建
将所有成员变量置为NULL0
main函数中创建堆变量再传入进行初始化,可以避免在销毁时对堆变量本身进行free

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

销毁
把动态申请的数组free掉,再把所有成员置为NULL0就可以了。

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

3. 2. 3 交换

交换两个数的位置,下面的函数需要用到。

void Swap(HPDataType* a, HPDataType* b)
{HPDataType tmp = *a;*a = *b;*b = tmp;
}

3. 2. 4 入堆与向上调整函数

这里就是堆和顺序表不同的部分了。

如果我们想向堆中加入一个数据,该怎么做?
直接将它放进数组就完成了吗?
当然不行,为了确保在插入一个数据之后这个数组还能表示一个堆,我们需要先实现一个向上调整算法,在向顺序表插入新数据后,将这个数据向上调整到合适的位置来确保堆的正确

//向上调整
void AdjustUp(HPDataType* arr, int child);

参数arr是堆的底层数组,参数child是要向上调整的数据。

要怎么向上调整?
我们可以找到child的父节点parent,如果parent对应的数据比child小,就说明现在已经不用再进行调整了,堆已经正确了。如果如果parent对应的数据比child大,就交换这两个数。如果调整一次之后堆仍然不正确,就继续上面的步骤直到堆正确
如图:
2

void AdjustUp(HPDataType* arr, int child)
{assert(arr);int parent = (child - 1)/2;	//父节点while (child)	//注意child可能会被调整到根节点,这时候就不能再调整了{if (arr[child] < arr[parent])	//如果条件语句不成立,就说明堆已经成型{Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;	//循环以上步骤}elsebreak;}
}

接下来看入堆
有以下两个步骤:

  1. 参考顺序表向数组尾插数据
  2. 向上调整
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);//插入数据if (hp->size == hp->capacity){//扩容int newcapacity = hp->capacity == 0 ? 2 : 2 * hp->capacity;HPDataType* new = (HPDataType*)realloc(hp->arr, newcapacity * sizeof(HPDataType));if (!new){perror("realloc");exit(1);}hp->arr = new;hp->capacity = newcapacity;}hp->arr[hp->size++] = x;//向上调整AdjustUp(hp->arr, hp->size - 1);
}

3. 2. 5 判空与取堆顶数据与堆的大小

判空

bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}

取堆顶数据
注意堆顶的下标是0。

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

堆的大小

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

3. 2. 6 出堆与向下调整

堆这个数据结构如果要删除数据只能从堆顶删除(可以类比现实中的堆,肯定不能从中间抽数据,不然堆就塌了)。那该怎么删除数据呢?
直接将堆顶元素删除吗?如果是这样,就要把后面所有元素向前挪动一位,而且两个孩子节点谁当父节点也是个问题。(在纸上画一画,很容易会发现这样删除真的很复杂)
所以更合适的办法应该是将堆顶元素与堆的最后一个元素交换位置,将最后一个元素删除,再进行向下调整算法

//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n);

2
向下调整和向上调整的思想是一样的,都是通过比较父节点与孩子节点并交换,直到堆是正确的。
注意这里父节点应该和较小的孩子节点进行比较与交换,来确保交换(如果发生交换)之后父节点大于孩子节点。

向下调整算法还有一个前提:左右子树必须是一个堆,才能调整。也就是说在调整之前,除了堆顶元素外,堆必须是正确的。
2

void AdjustDown(HPDataType* arr, int parent, int n)
{assert(arr);int child = 2 * parent + 1;//左孩子while (child < n)	//如果已经调整到了叶子节点,child就已经大于n了,不能再调整了{//找较小的孩子,child++就是右孩子了if (child + 1 < n && arr[child] > arr[child + 1])child++;//这里和向上调整就一样了,比较,交换,循环if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = 2 * parent + 1;}elsebreak;}
}

接下来就是出堆的完整函数了:

void HeapPop(Heap* hp)
{assert(hp && hp->size);//交换堆顶和最后一个元素Swap(&hp->arr[0], &hp->arr[hp->size - 1]);//删除最后一个元素(原来的堆顶)hp->size--;//向下调整AdjustDown(hp->arr, 0, hp->size);
}

3. 2. 7 大堆

实现小堆之后,只需要在两个调整算法中更改一下调整的判断条件(比如原来是父节点比子节点大就交换,更改成父节点比子节点小就交换)就能实现大堆了,这里就不再赘述了。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

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

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

相关文章

麦肯锡的金字塔原理:越简单,越高效

分享下金字塔原理&#xff0c;它由麦肯锡公司的芭芭拉明托在《金字塔原理》一书中首次提出&#xff0c;旨在帮助人们通过结构化的思维分析问题&#xff0c;思考问题和解决问题。它是一种方法&#xff0c;也是一种思想。 总的来说&#xff0c;金字塔原理就是将事情归纳出一个中…

[C++进阶]AVL树

前面我们说了二叉搜索树在极端条件下时间复杂度为O(n),本篇我们将介绍一种对二叉搜索树进行改进的树——AVL树 一、AVL 树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找效率低下。因此&#xff0c;两位…

随着访问范围的扩大 OpenAI o1-mini 现已向免费用户开放

上周&#xff0c;OpenAI 展示了其最新的大型语言模型&#xff08;LLM&#xff09;–OpenAI o1及其小兄弟 OpenAI o1-mini。该公司在公告中称&#xff0c;Plus 和 Team 用户可在公告发布之日起访问该模型。企业和教育用户将在本周获得该模型&#xff0c;而免费用户最终将获得 o1…

算法题总结(一)——二分查找专题

二分查找 我们二分查找的本质就是每次能够通过中间值来进行分割&#xff0c;能够比较判断&#xff0c;查找到或者接近需要的数据&#xff0c;然后把一部分的数据丢弃掉。 原题 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &…

一键更换软件源的工具——chsrc

前言 经常用pip&#xff0c;ubuntu的apt&#xff0c;或者centos的yum等包下载工具的人不可避免的一件事就是——“更换软件源”&#xff0c;因为以上三个包下载工具的软件源一般都是默认为国外的官方网站&#xff0c;由于国情问题&#xff0c;下载速度就会非常慢&#xff0c;所…

【Linux】生产者消费者模型:基于阻塞队列,使用互斥锁和条件变量维护互斥与同步关系

目录 一、什么是生产者消费者模型 二、为什么要引入生产者消费者模型&#xff1f; 三、详解生产者消费者模型 ​编辑 生产者和生产者、消费者和消费者、生产者和消费者&#xff0c;它们之间为什么会存在互斥关系&#xff1f; 生产者和消费者之间为什么会存在同步关系&…

Flet全平台开发:软件开发界勇士为Python语言补短板的一次极具挑战性的尝试、冲刺和华丽亮相

一、Flet创始人和开发者介绍、开发Flet的背景介绍 Flet 的创始人和开发者 Feodor Fitsner 是俄罗斯人&#xff0c;就职于微软。 Flet 的第一个版本于 2022 年 6 月发布。这是一个相对较新的库&#xff0c;它基于 Flutter 框架&#xff0c;首先支持的是用 Python 语言开发软件…

fiddler抓包03_汉化

Fiddler安装后为英文界面&#xff1a; 【汉化步骤】 ​① 下载汉化文件&#xff0c;链接: https://pan.baidu.com/s/1c13Dh--TwSCbwHykO8KAug?pwd8nvn 提取码: 8nvn ② 进入Fiddler目录&#xff0c;如我的安装在E:\test\Fiddler&#xff0c;将FiddlerTexts.txt复制到E:\tes…

大模型时代:普通人如何获利

随着人工智能技术的飞速发展&#xff0c;我们正步入一个以大模型为驱动力的新时代。这些大型语言模型&#xff0c;如GPT-3和BERT&#xff0c;已经在各个领域展现出惊人的能力&#xff0c;包括文本生成、翻译、问答等。这些技术的进步不仅改变了我们的生活&#xff0c;也为普通人…

【AI学习笔记】初学机器学习西瓜书概要记录(一)机器学习基础知识篇

初学机器学习西瓜书的概要记录&#xff08;一&#xff09;机器学习基础知识篇(已完结) 初学机器学习西瓜书的概要记录&#xff08;二&#xff09;常用的机器学习方法篇(待更) 初学机器学习西瓜书的概要记录&#xff08;三&#xff09;进阶知识篇(待更) 文字公式撰写不易&#x…

以root用户登陆ubuntu的桌面环境

前言 在学习Linux的时候&#xff0c;经常都需要使用sudo权限来对配置文件进行修改&#xff0c;常用的方法就是用vim编辑器在命令行界面进行修改&#xff0c;比如sudo vim /etc/profile&#xff0c;但我觉得每次都用命令行挺麻烦的&#xff0c;于是&#xff01;&#x1f913;我…

【STL】pair 与 map:基础、操作与应用

C 标准库中提供了许多用于处理数据结构的容器和工具。pair 和 map 是两个非常有用的工具&#xff0c;广泛应用于存储和处理关联数据。在本文中&#xff0c;我们将详细介绍 pair 与 map 的相关操作&#xff0c;并结合代码实例为读者提供清晰的理解。 pair&#xff1a;成对数据的…

Docker:SpringBoot项目创建Docker镜像并推送到阿里云容器镜像仓库

0. 准备工作 os&#xff1a;macos 15.0 jdk&#xff1a;1.8 docker&#xff1a;26.0.0 1. 阿里云容器镜像服务创建实例 创建个人版 个人实例创建成功 个人镜像加速器地址 2. 安装Docker Desktop Docker Desktop是Docker的一个集成工具&#xff0c;非必须&#xff0c;过程…

Vscode运行Python无法导入自己编写的包的解决方法

前言 在Vscode编辑器中&#xff0c;我经常用于编写Python代码&#xff0c;这一过程中&#xff0c;无论是导入第三方包还是Python内置的包&#xff0c;都未曾遇到过任何问题。然而&#xff0c;当我尝试导入一个跨文件自定义的包时&#xff0c;却遭遇了导入异常的问题。这一经历…

【例题】lanqiao153 洁净数

解题思路 通过枚举1-n的数&#xff0c;判断其是否为洁净数求解。 洁净数的判断&#xff1a;i%102判断此时的个位是不是2&#xff0c;ii//10把前一位移动到个位 # 小明非常不喜欢数字 2&#xff0c;包括那些数位上包含数字 2 的数。如果一个数的数位不包含数字 2&#xff0c;…

C++中的容器——vector

1. vector的介绍 vector&#xff1a;vector的底层实际上就是一个数组&#xff08;也称为顺序表&#xff09;&#xff0c;数据是连续存储在数组中的&#xff0c;因此vector是可以使用下标来进行访问的&#xff0c;但是它的大小并不是像数组一样是固定的&#xff0c;而是可以动态…

java基础知识20 Intern方法的作用

一 Intern方法作用 1.1 Intern方法 1.在jdk1.6中&#xff1a; intern()方法&#xff1a;在jdk1.6中&#xff0c;根据字符串对象&#xff0c;检查常量池中是否存在相同字符串对象 如果字符串常量池里面已经包含了等于字符串X的字符串&#xff0c;那么就返回常量池中这个字符…

从零开学C++:多态

引言&#xff1a;在我们去购买汽车票的时候&#xff0c;我们总会遇到成人全价&#xff0c;学生打折的情况。不同的对象&#xff08;成人、学生&#xff09;进行同一操作&#xff08;购买车票&#xff09;&#xff0c;得到不同的结果&#xff08;全价、打折&#xff09;&#xf…

2024年CAD图纸加密软件|加密图纸软件推荐:10款高效CAD加密软件

在当今数字化时代&#xff0c;CAD图纸已成为工程设计、建筑规划、机械制造等领域不可或缺的重要文件。然而&#xff0c;随着数据泄露和信息安全问题的日益严重&#xff0c;保护CAD图纸的安全性变得尤为重要。为了确保设计数据的安全&#xff0c;使用高效的CAD图纸加密软件成为了…

Stack类:常见方法讲解、使用场景、底层实现及算法问题

Stack 类是 Java 集合框架中的一个经典类&#xff0c;用于实现后进先出&#xff08;LIFO, Last In First Out&#xff09;数据结构。虽然 Stack 类作为一种直接的堆栈实现存在&#xff0c;但在开发中&#xff0c;Deque 或 LinkedList 更常被推荐用于堆栈的实现。不过&#xff0…