从二叉树到堆排序

2023-10-27

一:树

1.树的基本概念

请添加图片描述

  • 如图,这就是一颗普通的树,它是由一个根节点(A)和若干个子节点组成的,每个子节点也可能会有自己的子节点,就像树的树干上长出来许多分支一般,我们把这种数据结构叫做树。
  • 子树是不相交的,如果相交了就不是树,这也是区别树与非树的重要特点
  • 请添加图片描述
  • 如图,有子树相交,那么这个结构就不是树
  • 除了根节点外,每个节点仅有一个父节点
  • 一个有N个节点的树有N-1条边
  • 度:一个节点含有的子树的个数,比如A节点的度为5,因为它有A,B,C,D,E五个子树
  • 叶节点或终端节点:度为0的节点称为叶节点或终端节点,比如节点B,E,G,H,I,J,K,L,它们都没有子树,度为0,就是叶节点。
  • 双亲节点或父节点:A是B,C,D,E,F的父节点,C是G,H的父节点,很好理解
  • 孩子节点或子节点:B,C,D,E,F为A的子节点,G,H是C的子节点
  • 兄弟节点:具有相同父节点的节点互称兄弟节点,比如B,C,D,E,F为兄弟节点
  • 树的度:树中最大节点的度称为树的度,图中树的度为5
  • 树的高度或深度,树中节点的最大层次,图中树的高度为3
  • 森林:由m棵互不相交的树组合为森林

2.二叉树

  • 一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
  • 二叉树的特点:
  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。
  • 下面几个树都是二叉树
  • 请添加图片描述
    请添加图片描述

请添加图片描述

(1)最常见表示二叉树的方法

  • 现在我们知道了树这个数据结构,但如何表示又成了一个问题
  • 而最常用的表示方法就是双亲表示法
  • 在一个节点中储存数据和左右两个孩子节点
    请添加图片描述
    请添加图片描述
  • 结构体定义如下
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

(2)满二叉树

  • 定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
  • 下图就是一颗满二叉树。
    请添加图片描述

(3)完全二叉树

  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
  • 并且完全二叉树的前K-1层均满,最后一层可以不满,但从左到右必须是连续的
    • 这么说或许不太清晰,还是用图片来理解的快。请添加图片描述
  • 上图中左边的树不是完全二叉树,而右边的树是完全二叉树
  • 因为左边那棵树最后一层不是从左到右连续的,而右边的树是从左到右连续的

(4)二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=LogN

(5)存储结构

  • 二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
  • 顺序结构:
  • 顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,马上我们就能看到。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
  • 下图是一颗完全二叉树的顺序存储
    请添加图片描述
  • 链式结构:(即上文的双亲表示法)
  • 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

二:堆

1.逻辑结构与存储结构

  • 前面讲的关于树的概念都是为了堆作铺垫
  • 因为堆的逻辑结构其实就是一颗完全二叉树
  • 而堆的存储结构则是数组

2.堆的特性

  • 结构性:结构上是完全二叉树
  • 有序性:任意节点的值是其子树所有节点的最大(最小)值(不满足这点的只是普通的完全二叉树而不是堆)

堆分为大根堆和小根堆:

  • 小根堆:父节点小于等于子节点
  • 大根堆:父节点大于等于子节点
  • 如图就是一个小根堆

请添加图片描述

  • 如果假设父节点在数组中存储的下标为parent
  • 则左子节点下标leftchild=parent*2+1
  • 右子节点下标rightchile=parent*2+2
  • parent=(child-1)/2

3.向下调整算法

  • 此算法使用的前提是二叉树的左右子树恰好都为堆(大堆或小对均可),通过一次向下调整,可将此二叉树变为大堆或小堆
  • 举个栗子(以小堆为栗)
    请添加图片描述
  • 如图,根节点27的左右子树均为小堆
  • 向下调整算法可分为这几步:
  • (1)选出左右子节点中小的那一个
  • (2)将小的子节点与父节点比较
    a.如果小的子节点比父节点小,则跟父节点交换,并且把原来子节点的位置当做父节点继续往下调整
    b.如果小的子节点比父节点大,则不需要处理,此时整个树已经是小堆了
    请添加图片描述

代码如下:

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjustDown(int* 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[child] > a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

4.建堆

  • 可以看到,向下调整算法虽然能够帮助我们将普通的完全二叉树变为堆,但是必须要求左右子树均为小堆
  • 那么遇到不满足这样条件的完全二叉树如何将其变为堆呢?
  • 可以从最后一个非叶子节点,从后往前,依次进行向下调整
  • 如图,从最后一个非叶子节点34开始往前依次进行向下调整4次,即可将最初的完全二叉树变为一个大堆
    请添加图片描述
  • 代码如下:
int main()
{
	int a[] = {37,49,28,34,27,19,25,15,65 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
}

5.堆排序

  • 重头戏来了,我们现在终于建好了堆,可以进行堆排序了
  • 先想想,如果我们想要升序排序,应该建大堆还是小堆呢?
  • 如果建小堆,我们可以得到一个最小的数,但剩下的数据如何排序,再次建堆得出次小的数?假设我们已知建堆的时间复杂度为O(N),排一个数就建一次堆,那最终堆排序的时间复杂度就为O(N2)了,那么为什么不直接选择排序或者冒泡排序呢?
  • 所以我们升序排序需要建大堆
  • 建完大堆后将最大的数(根节点)和最后一个数互换,把剩下N-1个数看成新的堆,进行一次向下调整,选出次大的数,一次向下调整复杂度为O(logN),一共需要调整N-1次,加上开始建堆的O(N),最终时间复杂度为O(NlogN)
  • 下图为堆排序过程

请添加图片描述

  • 代码如下:
int main()
{
	int a[] = {37,49,28,34,27,19,25,15,65 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		swap(&(a[0]), &(a[end]));
		AdjustDown(a, end, 0);
		end--;
	}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从二叉树到堆排序 的相关文章

随机推荐

  • ffmpeg vscode debug编译错误【已解决】

    1 make no targets Stop 修改方式 make j N 这个N查看本机有多少个核 问题查找参考链接 https github com openresty openresty issues 22 2 the EAI MEMO
  • 线性回归和逻辑回归(LR)

    回归就是输出值是连续的而不是离散的 如果是离散值 就是分类问题 1 线性回归 1 定义 给定数据集D x1 y1 x2 y2 线性回归尝试学习到一个线性模型 尽可能地输出正确标记 线性回归无非就是在N维空间中找一个形式像直线方程一样的函数来
  • 如何用3个月零基础入门网络安全?

    一 自学网络安全学习的误区和陷阱1 不要试图先成为一名程序员 以编程为基础的学习 再开始学习 我在之前的回答中 我都一再强调不要以编程为基础再开始学习网络安全 一般来说 学习编程不但学习周期长 而且实际向安全过渡后可用到的关键知识并不多 一
  • React实现自定义双向数据流

    ng是双向数据流 VM双向数据绑定 而react与vue都是单向数据流 model层的数据流向view层 今天 我们就尝试自定义实现双向数据流 案例 组件中通过监听input内容变化 进而赋值 class Bar extends React
  • windows下使用cmake+mingw配置makefile(一)

    1 下载Cmake 并配置环境变量 下载链接 https cmake org download 环境变量略 2 生成Makefile 1 新建 hello 文件夹 在hello中创建hello c测试程序 mkdir hello cd C
  • Git 在AS上的操作总结+图解(仓库创建,分支的创建,切换,更新,合并,版本回退)

    简述 本文主要是按顺序进行描述的 创建仓库 创建项目 关联并提交 创建分支 将分支合并到主分支上去 版本的回滚 分支的更新 以及一些注意事项 使用AS创建一个项目 1 创建本地仓库 就会在选中的目录下面创建一个git仓库 关联本地库成功之后
  • day87(6.7函数的重载)

    1 函数的重载 函数的重载就是在同一个类中允许同时存在一个以上的同名函数 只要它们的参数个数或类型不同即可 在同一个类中可以定义多个 同名 方法 方法名重载 overload public class PrintStream public
  • Python兼职半月赚了5570元:边学习边赚钱真的很爽!

    前几天去参加朋友聚会 还没聊几句 就看到阿杰手机 叮 地一声 弹出一条推送 支付宝已到账 5570元 我开玩笑说 你暑假都有这么多生活费啊 羡慕了 快乐都是别人的 没想到阿杰说 除了管爸妈要生活费 我还不能搞点副业儿养活自己吗 我不酸 仔细
  • 尽量使用 useReducer,不要使用 useState

    原文 useReducer don t useState 本文难度 入门级别 本文默认你已经大概了解过 React Hooks 如果不了解可以先看看 ReactJS 的文档 当开发者们开始在他们的应用中使用 React Hooks API
  • DSP28335的RS232串口通讯试验

    目录 前言 一 理论部分 基本概念 SCI数据格式 管脚定义 逻辑电平规定 波特率 二 F28335配置RS232串口通讯 DSP28335SCI控制框图 寄存器配置 三 验证 验证思路 试验环境 关键程序 试验结果 前言 串口通信 Ser
  • JS——面向对象

    文章目录 1 class继承 1 class继承 定义一个类 属性和方法 定义一个学生类 class Student constructor name this name name hello alert Hello var xiaomin
  • 基于机器视觉的水果检测算法实现

    一 摘要 这是一款基于卷积神经网络和数字图像处理的智能水果检测和分类系统 由检测 分类两个部分组成 通过互联网下载和使用多媒体处理工具对水果拍摄视频剪辑处理得到大量水果图片 对图片进行标定获得数据集 并将数据集分成训练集和测试集 检测部分使
  • 基于二阶锥规划(SOCP)松弛和线性离流的配电网规划(DNP)方法示例(Matlab代码实现)

    目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 1 概述 配电网最优潮流 Optimal Power Flow OPF 问题是指在满足一定约束条件的情况下 通过控制配电网中的可控变量 使配电网达到优化运行的目的 由于OPF
  • 手把手教你搭建优雅的ssm框架(完整)

    直接开始搭建 使用的是idea工具 新建一个maven项目 加入框架支持 点开目录后 你会发现里面缺少目录 我们自己创建一下 在pom xml中添加依赖 这些依赖必不可少 后面可以根据需求自己添加
  • 第十四届蓝桥杯国赛python青少组题目

    LQGS14PB01 时间限制 3000MS 内存限制 589824KB 题目描述 编程实现 注 input 输入函数的括号中不允许添加任何信息 给定一个字符串S S长度 lt 100 统计字符串中字母一共有多少个 例如 S 1Abb 其中
  • keil 软件如何生成.hex文件

    1 当程序编译通过没错误了 按照下图步骤勾选 点击魔术棒 output界面 勾选hex 2 勾选完点击ok 再对程序进行编译 出现下图内容 则生成hex文件成功
  • C++ 学习笔记(二)

    C 继承与派生 1 公有继承 是指在源生一个类时继承方式为public的继承方式 在public继承方式下 基类成员在派生类中的访问权限为 基类的公有和保护成员的访问属性在派生类中不变而基类的私有成员不可访问 即基类的公有成员和保护成员被继
  • Vue中 element的table表格导入 与 导出为excel表格的实现

    Vue中 element的table表格导入 与 导出为excel表格的实现 一 导入 2 1 安装xlsx插件 2 2 新建导入功能组件 2 3 注册全局的导入excel组件 2 4 创建导入路由组件 2 5 封装导入接口 实现excel
  • QM二面

    目录 如何在笔记本上添加永久路由 扩展 Linux如何查看并杀死进程 模拟场景 动态路由协议和DNS的共同之处 如何在笔记本上添加永久路由 route print 查看路由表 添加路由的命令 route add 网段 mask 子网掩码 网
  • 从二叉树到堆排序

    目录 一 树 1 树的基本概念 2 二叉树 1 最常见表示二叉树的方法 2 满二叉树 3 完全二叉树 4 二叉树的性质 5 存储结构 二 堆 1 逻辑结构与存储结构 2 堆的特性 3 向下调整算法 4 建堆 5 堆排序 一 树 1 树的基本