数据结构初阶--二叉树介绍(基本性质+堆实现顺序结构)

news2024/12/23 10:31:55

树的基本概念和结构

树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2
叶节点或终端节点:度为0的节点称为叶节点; 如上图:D、F、G、H为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:A、B…等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为2
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4(根节点的高度记为1)
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林树的表示——左孩子右兄弟表示法

树的表示——左孩子右兄弟表示法

树的表示方法有很多,由于树不是一种线性的结构,所以表示起来会显得有些复杂,最常用的就是左孩子右兄弟表示法
左孩子右兄弟表示法是节点中保存第一个孩子的节点的指针,还有一个指针指向下一个兄弟节点。

template <class DateType>
struct Node
{
	Node* firstChild; // 第一个孩子结点
	Node* pNextBrother; // 指向其下一个兄弟结点
	DataType data; // 结点中的数据域
};

二叉树的概念及性质

二叉树的概念

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。

注意

  1. 二叉树的度不超过2
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

特殊的二叉树

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

完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

二叉树的性质

  • 若规定根节点的层数为1,则一棵非空二叉树的第n层上最多有2^(n-1)个结点

  • 若规定根节点的层数为1,则深度为n的二叉树的最大结点数是2^n-1

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

  • 树中父节点与子节点的关系
    • leftChild = parent*2+1
    • rightChild = parent*2+1
    • parent = (child-1)/2

二叉树的顺序结构及实现

二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。

可以看出,只有完全二叉树可以很充分地利用空间,普通二叉树会浪费很大的空间。

堆的概念以及结构

堆的性质:

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

堆的实现(小堆为例)

堆的框架

由于堆是用数组来进行存储的,所以这里的结构和顺序表有些类似,逻辑上是堆,物理上是一种数组的形式。

template <class DateType>
//小堆的实现
class MinHeap
{
public:
private:
	int size;
	int capacity;
	DateType* data;
};

堆的初始化

堆的初始化和顺序表的很相似基本上什么都不用做,只要指针置空,大小和容量置0即可。

//初始化堆,指针置空,大小和容量置0即可
	MinHeap()
	{
		this->size = this->capacity = 0;
		this->data = NULL;
	}
	//初始化堆,大小为n
	MinHeap(int n)
	{
		this->data = new DateType[n];
		this->capacity = n;
		this->size = 0;
	}

交换函数

//交换函数
	void Swap(DateType* x, DateType* y)
	{
		DateType tmp = *x;
		*x = *y;
		*y = tmp;
	}

向下调整算法

算法作用:将一个根节点的左右孩子均为大堆(小堆)的完全二叉树(非堆)堆调整成大堆(小堆)。

算法思路:以调整小堆为例,从根结点处开始,选出左右孩子中值较小的孩子。让小的孩子与其父亲进行比较。若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

前提条件:对于大堆,根节点的左右孩子都必须是一个大堆;对于小堆,根节点的左右孩子都必须是小堆。

具体例子:

//向下调整算法(小堆)
	void AdjustDown(int n, int parent)
	{
		//child记录左右函数中值较小的孩子的下标
		int child = 2 * parent + 1;//先默认其左孩子的值比较小
		while (child < n)
		{
			//右孩子存在并且比左孩子还小
			if (child + 1 < n && data[child + 1] < data[child])
			{
				child++;//较小的孩子改为右孩子
			}
			//左右孩子中较小孩子的值比父结点还小
			if (data[child] < data[parent])
			{
				//将父结点和较小的子结点交换
				Swap(&data[child], &data[parent]);
				//继续向下进行调整
				parent = child;
				child = 2 * parent + 1;
			}
			else
			{
				//已成堆
				break;
			}
		}
	}

向上调整算法

算法作用:向上调整算法就是在插入一个节点后为了使堆依旧保持原来的大堆或者小堆的一个调整算法。先将该节点与父亲节点比较,如果比父亲节点大就交换(原本是大堆)否则就不交换,直到交换到根节点为止。

当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。

算法思路:

将目标结点与其父结点比较。若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小堆了。

具体例子:

//向上调整算法(小堆)
	void AdjustUp(int child)
	{
		int parent = (child - 1) / 2;
		//调整到根结点的位置截止
		while (child > 0)
		{
			//孩子结点的值小于父结点的值
			if (data[child] < data[parent])
			{
				//将父结点与孩子结点交换
				Swap(&data[child], &data[parent]);
				//继续向上进行调整
				child = parent;
				parent = (child - 1) / 2;
			}
			else//已成堆
			{
				break;
			}
		}
	}

堆的创建

对于给定的一个数组,我们如何把他构建成大堆或者小堆呢?

堆的构建有两种方法:
第一种:从最后一个非叶子节点开始向下调整(从后往前遍历,向下调整)

第二种:从第二个节点往后开始向上调整(从前往后遍历,向上调整)

为什么呢?答案很简单,因为堆的向下和向上调整要求左右子树必须都是堆,只有这样才能保证一个无序的树,按照这种方式遍历,它的左右子树是一个堆。

上面说到,使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意树调整为堆呢?
方法一,我们只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可。

注意:最后一个非叶子节点的计算:假设一共有n个节点,最后一个节点的小标为n-1,最后一个非叶子节点就是最后一个节点的父节点,因此,最后一个非叶子节点的下标为:(n-2)/2

方法二:我们只需要从正数第二个结点开始,从前向后,按下标,依次向上调整即可

第一种代码实现

//创建堆
	//利用向下调整算法创建堆(小堆)
	void HeapCreate_down(int n)
	{
		//利用向下调整算法,从第一个非叶子结点开始调整。
		int i = (n - 2) / 2;
		//建小堆 排降序  建大堆 排升序
		for (; i >= 0; i--)
		{
			AdjustDown(i, n);
		}
	}

第二种代码实现

//利用向上调整算法创建堆(小堆)
	void HeapCreate_up(int n)
	{
		int i = 0;
		//建小堆 排降序  建大堆 排升序
		for (i = 1; i < n; i++)
		{
			//建大堆 向下调整
			AdjustUp(i);
		}
	}

堆的插入

堆的插入和顺序表的尾插有些相似,要考虑扩容的问题,有一点不同的是堆在插入后要进行向上调整,也就是向上调整算法,保持原来的堆的性状。

void HeapPush(DateType val)
	{
		if (this->capacity == this->size)
		{
			int newCapacity = this->capacity == 0 ? 4 : 2 * this->capacity;
			DateType* tmp = (DateType*)realloc(this->data, newCapacity * sizeof(DateType));
			if (tmp == NULL)
			{
				cout << "realloc分配失败" << endl;
				exit(-1);
			}
			this->data = tmp;
			this->capacity = newCapacity;
		}
		this->size++;
		this->data[this->size - 1] = val;
		//向上调整
		AdjustUp(this->size - 1);
	}

堆的删除

我们规定,堆的删除在头部进行,所以堆的删除也和顺序表的头删有些相似,要对大小进行断言,确保堆的大小不为0。但是堆不能直接在头部进行删除,这样会破坏堆的结构,又要重新建堆的时间复杂度是O(n)(后面会证明),这样就显得很麻烦。
于是就有新的一种方法,把堆顶的数据和堆尾的数据先进行交换,然后再把堆尾的数据删除,这样堆的结构就没有完全破坏,因为堆顶的左子树和右子树都是大堆,我们可以进行向下调整就可以恢复堆的形状了,向下调整算法的时间复杂度是堆的高度次,即O(log(h+1))。显然,下面这种算法更优。

代码实现如下:

//堆的删除
	void HeapPop()
	{
		if (HeapEmpty())
		{
			cout << "空堆,无法删除" << endl;
		}
		//把最后一个数替换堆顶的数,然后再进行向下调整
		Swap(&data[0], &data[this->size - 1]);
		this->size--;
		//向下调整
		AdjustDown(this->size, 0);
	}

堆的元素个数

//堆的元素个数
	int HeapSize()
	{
		return this->size;
	}

堆的销毁

堆的销毁就是对动态申请的空间进行释放,防止内存泄漏,其实和顺序表的销毁很相似。

//堆的销毁
	void HeapDestroy()
	{
		delete[] this->data;
		size = capacity = 0;
	}

打印堆的数据

void PrintHeap()
	{
		for (int i = 0; i < this->size; i++)
		{
			cout << data[i] << " ";
		}
		cout << endl;
	}

完整代码以及测试

 

#define _CRT_SECURE_NO_WARNINGS #include<iostream> //引入头文件 #include<string>//C++中的字符串 using namespace std; //标准命名空间 template <class DateType> //小堆的实现 class MinHeap { public: //初始化堆,指针置空,大小和容量置0即可 MinHeap() { this->size = this->capacity = 0; this->data = NULL; } //初始化堆,大小为n MinHeap(int n) { this->data = new DateType[n]; this->capacity = n; this->size = 0; } //交换函数 void Swap(DateType* x, DateType* y) { DateType tmp = *x; *x = *y; *y = tmp; } //向下调整算法(小堆) void AdjustDown(int n, int parent) { //child记录左右函数中值较小的孩子的下标 int child = 2 * parent + 1;//先默认其左孩子的值比较小 while (child < n) { //右孩子存在并且比左孩子还小 if (child + 1 < n && data[child + 1] < data[child]) { child++;//较小的孩子改为右孩子 } //左右孩子中较小孩子的值比父结点还小 if (data[child] < data[parent]) { //将父结点和较小的子结点交换 Swap(&data[child], &data[parent]); //继续向下进行调整 parent = child; child = 2 * parent + 1; } else { //已成堆 break; } } } //向上调整算法(小堆) void AdjustUp(int child) { int parent = (child - 1) / 2; //调整到根结点的位置截止 while (child > 0) { //孩子结点的值小于父结点的值 if (data[child] < data[parent]) { //将父结点与孩子结点交换 Swap(&data[child], &data[parent]); //继续向上进行调整 child = parent; parent = (child - 1) / 2; } else//已成堆 { break; } } } //创建堆 //利用向下调整算法创建堆(小堆) void HeapCreate_down(int n) { //利用向下调整算法,从第一个非叶子结点开始调整。 int i = (n - 2) / 2; //建小堆 排降序 建大堆 排升序 for (; i >= 0; i--) { AdjustDown(i, n); } } //利用向上调整算法创建堆(小堆) void HeapCreate_up(int n) { int i = 0; //建小堆 排降序 建大堆 排升序 for (i = 1; i < n; i++) { //建大堆 向下调整 AdjustUp(i); } } //堆的插入 void HeapPush(DateType val) { if (this->capacity == this->size) { int newCapacity = this->capacity == 0 ? 4 : 2 * this->capacity; DateType* tmp = (DateType*)realloc(this->data, newCapacity * sizeof(DateType)); if (tmp == NULL) { cout << "realloc分配失败" << endl; exit(-1); } this->data = tmp; this->capacity = newCapacity; } this->size++; this->data[this->size - 1] = val; //向上调整 AdjustUp(this->size - 1); } //堆的删除 void HeapPop() { if (HeapEmpty()) { cout << "空堆,无法删除" << endl; } //把最后一个数替换堆顶的数,然后再进行向下调整 Swap(&data[0], &data[this->size - 1]); this->size--; //向下调整 AdjustDown(this->size, 0); } //判断堆是否为空 int HeapEmpty() { return this->size == 0; } //堆的元素个数 int HeapSize() { return this->size; } //堆的销毁 void HeapDestroy() { delete[] this->data; size = capacity = 0; } //打印堆的数据 void PrintHeap() { for (int i = 0; i < this->size; i++) { cout << data[i] << " "; } cout << endl; } private: int size; int capacity; DateType* data; }; int main() { MinHeap<int> minheap; minheap.HeapPush(12); minheap.HeapPush(43); minheap.HeapPush(56); minheap.HeapPush(11); minheap.HeapPush(2); minheap.HeapPush(35); cout << minheap.HeapSize() << endl; minheap.PrintHeap(); cout << "------------------" << endl; minheap.HeapPop(); minheap.HeapPop(); cout << minheap.HeapSize() << endl; minheap.PrintHeap(); cout << "------------------" << endl; MinHeap<int> minheap2(3); minheap2.HeapPush(24); minheap2.HeapPush(56); minheap2.HeapPush(11); minheap2.HeapPush(29); minheap2.HeapPush(1); minheap2.HeapPush(38); minheap2.HeapPush(22); cout << minheap2.HeapSize() << endl; minheap2.PrintHeap(); system("pause"); return EXIT_SUCCESS; }

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

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

相关文章

Unity笔记(13):Android Movement of Characters[2D]

目录 1、搭建一个测试场景 2、建立画布设置移动按钮 3、编写脚本绑定按钮 AxisTouchButton &#xff1a;ButtonHandler &#xff1a; 4、编写脚本绑定角色 原来的按键移动 修改为触摸按钮 5、导出为APK文件&#xff0c;手机下载进行测试 1、搭建一个测试场景 2、建立画…

【正点原子FPGA连载】第二十四章 双路高速DA实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十四章 双路…

【EXCEL拦路虎】解决一些常遇到的excel问题

【问题一】 解决.CSV文件转为excel文件乱码问题 方法二&#xff1a;参考 解决Excel打开CSV文件中文乱码问题 【问题二】 解决内容过长后面的空白表格被前一列的字符填满&#xff0c;&#xff08;内容过长就会覆盖前后的单元格&#xff09;&#xff0c;如下图&#xff1a; 解决…

vue 新增枚举类型栏位

dict-tag 标签新增枚举类型栏位 新增栏位数据字典 新增字典命名规范为coin_表字段名 新增字典枚举数据&#xff0c;key value Value标签格式为 值-key 如 1-成交 分别对应的新增为两张表&#xff1a; Sys_dict_type --字典类型 Sys_dict_data --字典数据 前端栏位 &l…

数据库学习

数据是描述事务的符号记录&#xff0c;包括数字、文字、图像、音频等&#xff0c;以“记录”的形式按统一的格式进行存储&#xff1b;表将不同的记录组织在一起&#xff0c;用来存储具体的数据&#xff1b;数据库是表的集合&#xff0c;是存储数据的仓库&#xff0c;它以一定的…

基于粒子群算法的电力系统无功优化研究(IEEE14节点)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4cb;&#x1f4cb;&#x1f4cb;本文目录如下&#xff1a;⛳️⛳️⛳️ ​ 目录 1 概述 2 无功优化数学模型 3 …

剑指 Offer 15. 二进制中1的个数

一、题目描述 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 二、示例 示例 1&#xff1a; 输入&#xff1a;n 11 (控制…

基于SSM的邮箱客户端的设计与实现

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1 选题及意义 1 1.2 邮箱发展现状 1 1.2.1开源邮件系统 2 1.2.2国外商业邮件系统 2 1.2.3国内商业邮件系统 2 1.3研究内容 4 第2章 相关技术的理论概述 5 2.1 JSPServlet技术优势 5 2.2Mysql数据库的介绍 6 2.3 tomcat服务器介绍 6 2…

mmdetection3d nuScenes (持续更新)

Mmdetection3d集成了大量3D深度学习算法&#xff0c;其中很大一部分可以在智能驾驶nuScenes数据集上运行。在算法应用nuScenes数据之前&#xff0c;mmdetection3d提供了相应的预处理程序。关于nuScenes的详细介绍请参考本博客之前的文章nuScenes数据集详细介绍_Coding的叶子的博…

react+node.js+mysql 前后端分离项目 宝塔面板 部署到腾讯云服务器

目录一. 前端项目部署1.登录宝塔面板并安装环境依赖2. 打包上传&#xff0c;部署前端项目3.端口放行二. 后端项目部署1.上传后端项目2. PM2中添加项目三. mysql1.宝塔面板 添加数据库2.导入sql文件一. 前端项目部署 1.登录宝塔面板并安装环境依赖 命令行第一行输入刚才复制的内…

你是否还记得有一款游戏叫做“魔塔”?

目录 前言 正文 游戏介绍&#xff1a; 游戏开发制作流程 1.收集素材 2.创建攻击函数 2.1 定义两个对象&#xff08;主角和怪物&#xff09; 2.2 函数输出为【0】表示打不过&#xff0c;胜利输出受损生命值&#xff0c;设置 cancel 可以撤销本次战斗即回到之前状态 2.…

pinia安装使用

pinia中文文档 目录 一.pinia简介 二. pinia安装 三.pinia使用 1.main.js中创建pinia实例 2.创建store状态库 定义state state的读写 state响应式解构 state的修改 Getters的使用 Pinia中Store的互相调用 一.pinia简介 Pinia是vue生态里Vuex的替代者&#xff0c;一个…

MySQL笔记【面试】

MySQL笔记【面试】前言推荐MySQL笔记最后前言 以下内容源自A minor 仅供学习交流使用 推荐 MySQL MySQL笔记 【MySQL】基础使用&#xff08;一&#xff09;&#xff1a;支持的数据类型 【MySQL】基础使用&#xff08;二&#xff09;&#xff1a;常用 SQL 语句大全 【MyS…

推荐系统-概述:基本架构

提纲 过去八九年在广告、生活服务、电商等领域从事大数据及推荐系统相关工作&#xff0c;近来打算对过去的工作做一个系统性的梳理。一方面帮自己查缺补漏、进行更深入的学习&#xff1b;另一方面也希望能通过博客结交同好&#xff0c;增进交流。 这一博客系列以介绍推荐系统为…

Arduino开发实例-旋转编码器RGB-LED调光

旋转编码器RGB-LED调光 在本文中,将使用 Arduino 和旋转编码器进行 RGB LED 颜色控制。 我们将旋转旋转编码器来分配值。 红色、绿色和蓝色将合并以显示基于该值的全新颜色。 在这里,使用具有红色、绿色和蓝色的单 RGB 颜色 LED,也可以使用长 RGB LED 灯条。 此外,还可以使…

「Redis」02 Redis中的数据类型(含Redis6.0:Bitmaps、HyperLogLog、Geospatial)

笔记整理自【尚硅谷】Redis 6 入门到精通 超详细 教程 Redis——Redis中的数据类型 0. 键 (key) 操作 keys *&#xff1a;查看当前库所有 keyexists key&#xff1a;判断某个 key 是否存在type key&#xff1a;查看你的 key 是什么类型del key &#xff1a;删除指定的 key 数…

[附源码]Python计算机毕业设计Django工程施工多层级管理架构

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

VMware的安装、配置及其Linux的安装、简单配置

安装VMware 1、找到下载好的安装包&#xff0c;双击 2、点击下一步 3、勾选 我接受许可协议中的条款 4、选择合适的路径安装 5、取消勾选项&#xff0c;如图所示 6、下一步 7、安装 8、等待&#xff08;等待半分钟左右&#xff09; 8、点击许可证 9、复制下面其中的密码 key…

centos7磁盘扩容(虚拟机Mac m1)

为了安装HDP3.1.4(Ambari2.7.4)弄了三台虚拟机&#xff0c;但安装完mysql和操作完前期准备后&#xff0c;上传ambari&#xff0c;HDP&#xff0c;HDP-UTILS安装包时&#xff0c;磁盘居然不够了&#xff0c;又是一顿折腾...... 第一种在原来磁盘上扩大存储 1.虚拟机磁盘大小设…

借助云的力量,重塑企业的现在和未来——亚马逊云科技re:Invent

在2022亚马逊云科技re:Invent全球大会的第二天&#xff0c;亚马逊云科技首席执行官Adam Selipsky发表了“如何借助云的力量&#xff0c;在未知领域抓住机遇并茁壮成长”的主题演讲。在两个小时的演讲中&#xff0c;Adam重点围绕数据、安全、计算性能和行业应用等4个主题发布了多…