【二叉树---堆】

news2024/12/26 11:35:45

二叉树---堆

  • 一、树的概念及结构
    • 1. 树的概念
    • 2.树的相关概念
    • 3.树的表示
  • 二、二叉树
    • 1.二叉树的概念
    • 2.特殊的二叉树
    • 3.二叉树的性质
  • 三、堆
    • 1.堆的概念及结构
    • 2.堆的实现
      • (1)函数的声明
      • (2)函数的实现
      • (3)测试堆的基本操作
    • 3.堆的应用
      • (1)堆排序
      • (2)TopK

一、树的概念及结构

1. 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
有一个特殊的结点,称为根结点,根节点没有前驱结点。除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。

2.树的相关概念

在这里插入图片描述
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6.
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

3.树的表示

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

		typedef int DataType;
		struct Node
		{
		 	struct Node* _firstChild1; // 第一个孩子结点
		 	struct Node* _pNextBrother; // 指向其下一个兄弟结点
		 	DataType _data; // 结点中的数据域
		};

这个表示法是每次都找左边的第一个孩子,让孩子的兄弟指针去找其他的兄弟节点。

二、二叉树

1.二叉树的概念

一棵二叉树是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

在这里插入图片描述

从上图可以看出:

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

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

在这里插入图片描述

2.特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

在这里插入图片描述

在这里插入图片描述

3.二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1) 个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h - 1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0 , 度为2的分支结点个数为 n2 ,则有 n0 = n2+1.
  4. 若规定根节点的层数为1,具有N个结点的满二叉树的深度,h= log(N+1). (ps: 是log以2为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为 i 的结点有:

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

三、堆

1.堆的概念及结构

如果有一个关键码的集合K = { k0,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K 2i+1且 Ki <= K 2i+2 (Ki >= K 2i+1 且 Ki >= K 2i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

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

例如小堆,在以10为父节点的子树中,它的孩子15和56都比它大;在以15为父节点的子树中,它的孩子25和30都比它大;另外,我们可以将堆的物理结构看作一个数组,实现堆的时候我们用数组模拟实现,但控制的其实是堆;
在这里插入图片描述
在这里插入图片描述

2.堆的实现

在用数组实现堆之前,我们应该知道两个性质:

  1. 知道父亲的下标 i ,我们可以求出它的左边第一个孩子的下标:i * 2 + 1;而右边的孩子下标为:i * 2 + 2;
  2. 知道孩子的下标 i ,我们可以求出它的父亲的下标:(i - 1) / 2;

(1)函数的声明

		typedef int HPDataType;
		
		typedef struct Heap
		{
			HPDataType* a;
			int size;
			int capacity;
		}HP;
		
		//向上调整---调大堆
		void AdjustUP(HPDataType* a, int child);
		
		//向下调整---调大堆
		void AdjustDown(HPDataType* a, int len, int parent);
		
		//向上/向下调整中交换
		void Swap(HPDataType* p1, HPDataType* p2);
		
		
		//初始化
		void HeapInit(HP* php);
		
		//入堆
		void HeapPush(HP* php, HPDataType x);
		
		//删除堆顶的数据
		void HeapPop(HP* php);
		
		//获取堆顶元素
		HPDataType HeapTop(HP* php);
		
		//获取数组模拟实现的堆的长度
		int HeapSize(HP* php);
		
		
		//判断空堆
		bool HeapEmpty(HP* php);
		
		//释放内存
		void HeapDestory(HP* php);

堆向下调整算法:现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

堆向上调整算法:从一个节点开始往上开始调整,这个节点一般是叶子,如果调的是大堆,那么就是如果这个节点的父亲比它小,那么就将这两个节点交换;堆向上调整算法的前提是:开始调整的节点上面必须是一个大/小堆;

下面看代码的具体实现:

(2)函数的实现

		//初始化
		void HeapInit(HP* php)
		{
			assert(php);
		
			php->a = NULL;
			php->size = 0;
			php->capacity = 0;
		}
		
		
		//判断空堆
		bool HeapEmpty(HP* php)
		{
			assert(php);
		
			return php->size == 0;
		}
		
		
		
		//向上/向下调整中交换
		void Swap(HPDataType* p1, HPDataType* p2)
		{
			HPDataType tmp = *p1;
			*p1 = *p2;
			*p2 = tmp;
		}
		
		
		//向上调整---调大堆
		void AdjustUP(HPDataType* a, int child)
		{
			//孩子节点找出父节点,从父节点往上调;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//如果孩子节点比父节点大,就交换它们的值,并让当前的父节点成为新的孩子节点
				if (a[child] > a[parent])
				{
					Swap(&a[child], &a[parent]);
		
					child = parent;
					parent = (child - 1) / 2;
				}
		
				//否则,当前的子树已是大堆
				else
				{
					break;
				}
			}
		}
		
		
		//向下调整---调大堆
		void AdjustDown(HPDataType* a, int len, int parent)
		{
			//先定义孩子的节点是父节点的左边第一个
			int child = parent * 2 + 1;
		
			while (child < len)
			{
				//找出孩子节点中较大的那个
				if (child + 1 < len && a[child + 1] > a[child])
				{
					child++;
				}
		
				//如果较大的孩子节点比父节点大,就交换它们的值,并让当前的孩子节点成为新的父节点
				if (a[child] > a[parent])
				{
					Swap(&a[child], &a[parent]);
		
					parent = child;
					child = parent * 2 + 1;
				}
				
				//否则,说明当前的子树已经是大堆
				else
				{
					break;
				}
			}
		}
		
		
		
		//入堆
		void HeapPush(HP* php, HPDataType x)
		{
			assert(php);
		
			//扩容
			if (php->size == php->capacity)
			{
				int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
				HPDataType* newphp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
				assert(newphp);
		
				php->a = newphp;
				php->capacity = newcapacity;
			}
		
			php->a[php->size] = x;
			php->size++;
		
			//向上调整
			AdjustUP(php->a, php->size - 1);
			
		}
		
		
		//删除堆顶的数据
		void HeapPop(HP* php)
		{
			assert(php);
			assert(!HeapEmpty(php));
		
			Swap(&php->a[0], &php->a[php->size - 1]);
			php->size--;
		
			AdjustDown(php->a, php->size, 0);
		}
		
		
		//获取堆顶元素
		HPDataType HeapTop(HP* php)
		{
			assert(php);
			assert(!HeapEmpty(php));
		
			return php->a[0];
		}
		
		
		//获取数组模拟实现的堆的长度
		int HeapSize(HP* php)
		{
			assert(php);
		
			return php->size;
		}
		
		
		//释放内存
		void HeapDestory(HP* php)
		{
			free(php->a);
			php->a = NULL;
			php->capacity = 0;
			php->size = 0;
		}

(3)测试堆的基本操作

		void TestHeap()
		{
			HP hp;
			HeapInit(&hp);
		
			int a[] = { 65,100,70,32,50,60 };
			for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
			{
				HeapPush(&hp, a[i]);
			}
		
			while (!HeapEmpty(&hp))
			{
				int top = HeapTop(&hp);
				printf("%d\n", top);
				HeapPop(&hp);
			}
		
			HeapDestory(&hp);
		}

测试结果如下:

在这里插入图片描述

3.堆的应用

(1)堆排序

堆排序的思路是,首先要建立一个堆,如果是排升序,就建大堆,因为大堆中,大的在前面,每次让堆顶的数据与堆尾的数据的值进行交换,交换完长度减一,相当于最大的放到后面就不动了,然后再从堆顶开始向下调整,次大的调到堆顶,然后和倒数第二的数据的值进行交换…直到长度减到0.

这里建堆推荐使用向下建堆,因为向下建堆的时间复杂度为 O(N) ,而向上建堆的时间复杂度为 O(N * logN) ;

		//堆排
		void HeapSort(int* a, int len)
		{
		
			//向上建堆---大堆(时间复杂度:O(N * logN))
			/*for (int i = 1; i < len; i++)
			{
				AdjustUP(a, i);
			}*/
		
			//向下建堆---大堆(时间复杂度:O(N))
			for (int i = (len - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(a, len, i);
			}
		
			//每次交换第一个与最后一个的数据,交换完数据长度 -1 ,从下标 0 开始向下调整
			while (len)
			{
				Swap(&a[0], &a[len - 1]);
				len--;
				AdjustDown(a, len, 0);
			}
		}

其中实现的结果如下:

在这里插入图片描述

(2)TopK

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

先生成数据,这里只生成 100000 以内的数据,方便后面进行检验;

		void CreateNDate()
		{
			// 造数据,1000个
			int n = 1000;
			srand(time(0));
		
			//创建文档
			const char* file = "data.txt";
		
			//以write的方式打开文档
			FILE* fin = fopen(file, "w");
			assert(fin);
		
			//生成 100000 以内的随机数写入文档中
			for (size_t i = 0; i < n; ++i)
			{
				int x = rand() % 100000;
				fprintf(fin, "%d\n", x);
			}
		
			//关闭文档
			fclose(fin);
		}

寻找前 k 个最大的数据;

		void PrintTopK(int k)
		{
			//创建文档,已存在就忽略
			const char* file = "data.txt";
		
			//以只读的方式打开
			FILE* fout = fopen(file, "r");
			assert(fout);
		
			//开辟 k 个空间,即前 k 个数据的空间
			int* kminheap = (int*)malloc(sizeof(int) * k);
			assert(kminheap);
		
			//从文档中读取前 k 个数据,放入kminheap数组中
			for (int i = 0; i < k; i++)
			{
				fscanf(fout, "%d", &kminheap[i]);
			}
		
			// 建小堆
			for (int i = (k - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(kminheap, k, i);
			}
		
			// 继续读取文档中 N - K 个数据
			//如果读取到比堆顶大的数据,就直接覆盖堆顶的数据,然后向下调整
			//直到读取文档中的数据失败,即读取完全部数据
			int val = 0;
			while (!feof(fout))
			{
				fscanf(fout, "%d", &val);
				if (val > kminheap[0])
				{
					kminheap[0] = val;
					AdjustDown(kminheap, k, 0);
				}
			}
		
			//打印前 k 个
			for (int i = 0; i < k; i++)
			{
				printf("%d ", kminheap[i]);
			}
			printf("\n");
		}

这里上面只生成了 100000 以内的数据,所以我们手动改变文档中的值,随机将 5 个值的后面加上四个 4 ,即变成了最大的五个数,如下图,证明了我们的代码是正确的;

在这里插入图片描述

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

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

相关文章

chatgpt赋能python:Python多次运行的优势及方法

Python多次运行的优势及方法 随着Python在计算机领域的广泛应用&#xff0c;越来越多的企业和个人开始使用Python进行编程。在Python编程中&#xff0c;多次运行同一份代码不仅是常见的需求&#xff0c;而且有着众多的优势。 Python多次运行的优势 调试 在编程过程中&#…

chatgpt赋能python:Python安装EGG——一个简单的指南

Python安装EGG——一个简单的指南 如果你使用Python有一段时间了&#xff0c;你可能会遇到需要安装扩展包&#xff08;Package&#xff09;的情况。在Python中&#xff0c;这些扩展包的文件格式通常是.egg&#xff08;Easy Installable GZip&#xff09;。在本文中&#xff0c…

chatgpt赋能python:Python如何安装CSV模块

Python如何安装CSV模块 CSV是一种广泛使用的文件格式&#xff0c;用于存储表格数据。Python已经内置了CSV模块&#xff0c;可以轻松地读取和写入CSV文件。在这篇文章中&#xff0c;我们将关注如何安装CSV模块并开始与CSV文件进行交互。 什么是CSV&#xff1f; CSV是Comma Se…

chatgpt赋能python:如何安装pyecharts

如何安装pyecharts Pyecharts是一个基于echarts的数据可视化工具&#xff0c;它是Python语言的一个库&#xff0c;可以通过Python编程语言进行数据可视化&#xff0c;并且能通过交互式的方式展示出来。 在本文中&#xff0c;我们将介绍如何安装pyecharts&#xff0c;如果您是…

chatgpt赋能python:Python宏定义:编写高效、灵活和可维护的代码

Python宏定义&#xff1a;编写高效、灵活和可维护的代码 Python是一种非常流行的编程语言&#xff0c;它的简洁性、易读性以及灵活性使得它成为了众多开发者的首选。Python是一个强大的语言&#xff0c;它可以大大提高您的编程效率。Python的宏定义是一种非常有用的技巧&#…

【VB6|第18期】基于libxl导出Excel之导出失败的解决方案

日期&#xff1a;2023年6月12日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

康耐视Visionpro-插入脚本的标准流程 _ 简单方式

机器视觉海康Visionmaster-字符缺失缺陷检测 支持脚本添加的工具如下&#xff1a;添加的脚本只针对当前工具容器有效 Step - 1 &#xff1a; 制作ToolBlock&#xff0c;添加输入/输出等 Step - 2 &#xff1a; 新建Script Step - 3 &#xff1a; 检查界面终端 Step - 4 &am…

【黑马程序员 C++教程从0到1入门编程】【笔记4-2】C++核心编程(类和对象——运算符重载)(左操作数、右操作数)(仿函数)

文章目录 4 类和对象&#xff08;类属性【成员属性】&#xff0c;类函数【成员函数】&#xff09;4.5 运算符重载&#xff08;对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型&#xff09;4.5.0.1 可重载运算符和不可重载运算符4.5.0…

案例:创建一个学生管理系统(PXSCJ1)的数据库(SQL)

1、新建数据库&#xff1a;PXSCJ1 --创建数据库CREATE DATABASE PXSCJ1 --创建并确认属性&#xff1a;XSB、KCB、CJB&#xff08;以下代码用于2、3、4、5题&#xff09; use PXSCJ1 create table XSB (学号 char(6) primary key check(学号 like [0][8][1][12][0-9][0-9])…

SM3_Robotics,轴组函数调用

1轴组状态&#xff1a; AXIS_GROUP_REF_SM3 (FB) 2使能&#xff1a; MC_GroupEnable &#xff08;使能&#xff09; 默认在&#xff1a; MC_GroupDisable &#xff08;轴组关闭&#xff09;位置 1&#xff1a;用 MC_GroupEnable &#xff08;使能&#xff09;进入 Gro…

chatgpt赋能python:Python怎么定义主函数:完整指南

Python怎么定义主函数&#xff1a;完整指南 Python是当今最流行的编程语言之一&#xff0c;因为它提供了简单易学、高效率、高度可读性和可维护性的代码编写方式。在Python中定义主函数是一个重要的编程技能&#xff0c;使您能够将Python程序变成可执行的Python应用程序。在本…

chatgpt赋能python:Python多次输入——如何自动化处理数据输入

Python多次输入——如何自动化处理数据输入 作为一名有10年Python编程经验的工程师&#xff0c;我曾遇到过很多需要重复输入数据的情况。这不仅浪费时间&#xff0c;而且容易出错&#xff0c;影响我们的工作效率和准确性。作为程序员&#xff0c;我们需要借助Python的自动化技…

openGauss5 企业版之SQL语法和数据结构

文章目录 1.openGauss SQL 语法2. 数据类型2.1数值类型2.2 布尔类型2.3 字符类型2.4 二进制类型2.5日期/时间类型2.6 几何类型2.7 网络地址类型2.8 位串类型2.9 文本搜索类型2.10 UUID数据类型2.11 JSON/JSONB类型2.11 HLL数据类型2.12 范围类型2.13 索引2.14 对象标识符类型2.…

【MySQL】SQL的高阶用法

文章目录 函数聚合函数Count()Max()Min()Sum()Avg() 其他常用函数时间函数字符串函数数学函数 条件查询使用关系运算符查询使用IN关键字查询使用BETWEEN AND关键字查询使用空值查询使用AND关键字查询使用OR关键字查询使用LIKE关键字查询(模糊查询)使用LIMIT限制查询结果的数量使…

用ChatGPT生成测试数据

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 在之前的文章 用ChatGPT写一个数据采集程序 中&#xff0c;我们演示了如何用 ChatGPT 辅助编写代码。 除了直接让ChatGPT写代码&#xff0c;我们也可以让它生成一些开发中使用的测试数据。 比如在开发和测试时&…

Alloy Tutorial(3)Traces Modelling —— Cache Memory

文章目录 Cache Memory完整代码 Cache Memory //Addresses and data sig Addr {} sig Data {}//A cache system consists of main memory and cached memory, but mapping addresses to data one sig CacheSystem {var main, cache: Addr -> lone Data }//Initially there …

yolov5——从未见过注释比代码还多的源码解析You Only Look Once And You get it——训练部分

目录 一&#xff1a;前言 二&#xff1a;先介绍v5源码中必须知道的一些文件&#xff08;了解的可直接加入第三代码部分&#xff09; ​编辑 三&#xff1a;训练 参数配置 模式选择 搭建网络 加载预训练和自定义模型的参数 是否需要冻结层数 定义累计梯度的次数 设置…

零基础小白如何自学 Unity 游戏开发?(送 Unity 教程)

如何自学 Unity&#xff1f;初级阶段&#xff1a;学习编程语言初级阶段&#xff1a;编程实践中级阶段&#xff1a;继续学习编程语言 Unity 教程赠书活动内容简介作者简介赠书方式 如何自学 Unity&#xff1f; 有很多同学对 游戏开发 很感兴趣&#xff0c;但都不知道从何学起&a…

PostgreSQL如何查看事务所占有的锁?

表级锁命令LOCK TABLE 在PG中&#xff0c;显式地在表上加锁的命令为“LOCK TABLE”&#xff0c;此命令的语法如下&#xff1a; LOCK [TABLE] [ONLY] name [,...][IN lockmode MODE] [NOWAIT]语法中各项参数说明如下&#xff1a; name&#xff1a;表名lockmode&#xff1a;表…