数据结构------树

devtools/2025/1/15 13:47:10/

前言:前面我们学习了栈和队列。今天我们来学习一种新的数据结构---------树。

首先我们来了解一下树的概念。

1.树的概念与结构

前面我们学习过的顺序表,栈都是一种顺序结构。链表,队列是链式结构。今天学习的树也是一种链式结构。它是由n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做树,是因为它看起来像是一棵倒挂的树,也就是说它的根朝上,叶朝下。

. 有一个特殊的节点,称作根节点,没有前驱结点

. 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1,T2,T3...Tm,其中每一个集合Ti(1<=i<=m)又是一棵结构与树类似的子树。每棵子树的根节点有且只有一个前驱,可以有0个或多个后继节点,因此树是递归定义的

. 子树是不相交的

. 除了根节点,每个节点有且仅有一个父节点

. 一棵N个节点的树有N-1条边

树形结构

在这里插入图片描述


非树形结构

在这里插入图片描述

2. 树的相关术语

父结点/双亲结点若一个结点含有子结点,则这个结点称为其子结点的父结点。(A是B的父结点)

子结点/孩子结点一个结点含有子树的根结点称为该结点的子结点。(如上图:B是A的子结点)

结点的度一个结点有几个孩子,该结点的度就是多少

树的度一棵树中,最大结点的度就是树的度

叶子结点/终端结点度为0的结点称为叶子结点。(如J,K,L)

分支结点/非终端结点度不为0的结点。(B,C,D等)

兄弟结点具有相同父结点的结点互称为兄弟结点。(B,C,D)

结点的层次从根结点开始定义,根结点为第一层,以此类推

树的高度或深度树中结点的最大层次

结点的祖先从根到该结点所经分支上所有的结点

路径一条从树中任意结点出发,沿父结点-子结点连接,达到任意结点的序列。(如F到L的路径为:F-B-A-C-G-L)

子孙以某结点为根的子树中任意结点都称为该结点的子孙

森林由m(m>0)棵互不相交的树的集合称为森林

3.树的表示方法

树的表示方法有很多种:双亲表示法,孩子表示法,孩子双亲表示法以及孩子兄弟表示法。这里我们就了解最常用的孩子兄弟表示法

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

在这里插入图片描述

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

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

5.二叉树的概念与结构

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

在这里插入图片描述

5.1二叉树的特点

  1. 二叉树中不存在度大于2的结点

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

5.2 满二叉树

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

在这里插入图片描述

5.3 完全二叉树

完全二叉树是由满二叉树引出来的,对于深度为k,n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1至n的结点一 一对应时称之为完全二叉树满二叉树是一种特殊的完全二叉树

在这里插入图片描述

5.4 二叉树的性质

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

5.5 二叉树的存储结构

二叉树一般有两种存储结构,顺序结构和链式结构

5.5.1 顺序结构

顺序结构存储就是使用数组来存储一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费
在这里插入图片描述

看到这里,不知有没有小伙伴有一些疑问呢。使用数组存储,为什么二叉树的节点为空的的时候,还要为其保留一份空间呢?如果不保留的话,不就没有空间浪费了吗?这个问题问的非常好。那我们反向思考一下,这样做有什么用处呢?除根节点外,每个节点都有其父节点和子节点,那么我们使用数组来存储这些数据,怎么去确定其父节点和子结点的位置呢?我们能想到的只有数组下标了。现在我们再来看看刚才的问题,如果节点为空,在数组中我们不为其保留一份空间,那么我们还能表示出二叉树节点之中的关系吗?答案当然是不能。

5.5.2 链式结构

二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素之间的逻辑关系。通常是链表中每个节点由三个域组成,数据域和左右指针域,左右指针分别用来给出该节点左右孩子所在的链节点的存储地址

在这里插入图片描述

6. 堆的概念

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

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

在这里插入图片描述
在这里插入图片描述

6.1 堆的性质

. 堆中某个节点的值总是不大于或不小于其父节点的值

. 堆总是一棵完全二叉树

对于具有n个节点的完全二叉树,按照从上到下,从左至右的数组顺
序对所有节点从0开始进行编号,则对于序号为i的节点有:
1.若i>0,i位置节点的双亲序号:(i-1)/2;i=0,根节点,无双亲节点
2.若2*i+1<n,左孩子序号:2*i+1,否则,无左孩子
3.若2*i+2<n,右孩子序号:2*i+2,否则,无右孩子

6.2 堆的实现

6.2.1 堆的定义

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int capacity;//堆的容量int size;//堆中有效数据的个数
}Heap;

6.2.2 堆的接口

//堆的初始化
void HeapInit(Heap* php);//堆的销毁
void HeapDestroy(Heap* php);//堆中插入数据
void HeapPush(Heap* php, HPDataType x);//堆是否为空
bool HeapEmpty(Heap* php);//删除堆顶数据
void HeapPop(Heap* php);//获取堆顶数据
HPDataType HeapTop(Heap* php);//堆的大小
int HeapSize(Heap* php);

6.2.3 堆的初始化

//堆的初始化
void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->capacity = php->size = 0;
}

6.2.4 堆中插入数据

//堆的插入
void HeapPush(Heap* php, HPDataType x)
{assert(php);//增容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("HeapPush():realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}//插入数据php->a[php->size] = x;//先建堆再更新php->size的原因是需要使用子节点的下标求父节点的下标及比较子节点与父节点的大小,从而达到建堆的目的//满足堆的特性AdjustUp(php->a, php->size);//更新堆中有效数据的个数php->size++;
}

每一次插入数据,都需要检查容量是否足够,不够就需要扩容。第一次容量为0,给4个空间,否则就开辟2倍的空间,再插入数据,接下来就要进行建堆(小堆或大堆),最后,size自增

6.2.5 向上调整算法

void Swap(HPDataType* x, HPDataType* y)
{HPDataType tmp = *x;*x = *y;*y = tmp;
}
void AdjustUp(HPDataType* a, int child)
{//父节点的下标int parent = (child - 1) / 2;//while (parent >= 0)while (child > 0){// > 大堆// < 小堆if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

在这里插入图片描述

当child的值大于parent的值时,就交换child和parent的值,让child走到parent的位置,再更新parent的值。child=0就不用在进行调整了,此时已经是一个有效的堆结构了。如果不符合这个条件,就可以直接退出循环

6.2.6 堆是否为空

//堆是否为空
bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

size记录堆中数据的个数,当size为0时,说明堆为空

6.2.7 删除堆顶数据

//删除堆顶数据
void HeapPop(Heap* php)
{assert(php);//堆不能为空assert(!HeapEmpty(php));//根节点与最后一个节点交换Swap(&php->a[0], &php->a[php->size - 1]);//删除数据php->size--;//恢复堆结构AdjustDown(php->a, 0, php->size);
}

首先,堆不为空才能删除数据。让堆顶数据与堆中最后一个数据进行交换,再让堆中数据个数size-1,最后调整堆结构,使之成为一个有效的堆

6.2.8 向下调整算法(前提:左右子树必须是一个堆)

//恢复堆结构
void AdjustDown(HPDataType* a, int parent, int num)
{//左孩子的下标int child = 2 * parent + 1;while (child < num){//左右孩子比较大小if (child + 1 < num && a[child] < a[child + 1]){child++;}if (a[child] > a[parent]){Swap(&a[parent], &a[child]);parent = child;child = 2 * parent + 1;}else{break;}}
}

在这里插入图片描述

让最大的孩子节点与父节点进行交换,parent走到child的位置,更新child的位置。当child大于堆中数据的个数时,不再进行调整,如果已经是一个有效的堆,那么就跳出循环

6.2.9 获取堆顶数据

//获取堆顶数据
HPDataType HeapTop(Heap* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];
}

6.2.10 堆的大小

//堆的大小
int HeapSize(Heap* php)
{assert(php);return php->size;
}

6.2.11 堆的销毁

//堆的销毁
void HeapDestroy(Heap* php)
{assert(php);if (php->a != NULL){free(php->a);php->a = NULL;}php->capacity = php->size = 0;
}

a所指向的这块空间是动态开辟出来的,要还给操作系统,之后将size和capacity置为0

7. TOP-K问题

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

对于这种问题,我们能想到的直接方式就是对数据进行排序,但是如果数据量非常大的话,这种方式就不建议采取了,因为这么多的数据都不一定能够全部加载到内存中最好的方式就是用堆来解决

那我们应该想一想,我们应该建大堆还是建小堆呢?分情况讨论,如果要找前K个最大的数据,我们可以建小堆。让前K个数据入堆,后N-K个数据依次与堆顶数据进行比较,比堆顶数据大,就入堆。同理,找前K个最小的数据,我们应该建大堆,后N-K个数据依次与堆顶数据进行比较,比堆顶数据小的入堆

总结

. 找前K个最大的数据,建小堆

. 找前K个最小的数据,建大堆

举例:假如有10亿个数据,那么我们就需要多大的空间呢?我们可以简单的计算一下。

//10亿个字节
1GB=1024MB=1024*1024KB=1024*1024*1024Byte
//一个整型4个字节,10亿个整数就需要40亿个字节,也就是说我们需
要一下子开辟4GB的空间,这显然是不行的。

代码实现

#include<iostream>
//创建N个数据到文件中
void CreateData()
{int N = 100000;srand((unsigned int)time(NULL));FILE* file = fopen("data.txt", "w");if (file == NULL){perror("fopen fail");exit(-1);}int randnum = 0;for (int i = 0; i < N; ++i){randnum = rand() % 100000 + 1;fprintf(file, "%d\n", randnum);}fclose(file);file = NULL;
}void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}//恢复堆结构
void AdjustDown(int* minHeap, int parent, int num)
{//左孩子的下标int child = 2 * parent + 1;while (child < num){//左右孩子比较大小,建小堆if (child + 1 < num && minHeap[child] > minHeap[child + 1]){child++;}if (minHeap[child] < minHeap[parent]){Swap(&minHeap[parent], &minHeap[child]);parent = child;child = 2 * parent + 1;}else{break;}}
}
void TopK()
{FILE* fout = fopen("data.txt", "r");if (fout == NULL){perror("fopen fail");exit(1);}int k = 0;printf("请输入:");scanf("%d", &k);//开辟k个数据的存储空间int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail");return;}//前k个数据入堆for (int i = 0; i < k; ++i){fscanf(fout, "%d", &minHeap[i]);}//找前k个大数据,建小堆//找前k个小数据,建大堆for (int parent = (k - 1 - 1) / 2; parent >= 0; parent--){AdjustDown(minHeap, parent, k);}//N-K个数据中找比堆顶数据大的入堆int x = 0;while (fscanf(fout, "%d", &x)!=EOF){if (x > minHeap[0]){minHeap[0] = x;AdjustDown(minHeap, 0, k);}}for (int i = 0; i < k; ++i){printf("%d ", minHeap[i]);}fclose(fout);fout = NULL;free(minHeap);minHeap = NULL;
}
int main()
{//CreateData();TopK();return 0;
}

http://www.ppmy.cn/devtools/150690.html

相关文章

infinitetensor训练营-cuda1

一、基础概念剖析&#xff1a; 1、延迟&#xff1a;发送内存请求——>实际完成数据移动所需的时间。可以理解为家到公司的距离 2、带宽&#xff1a;单位时间移动数据量&#xff0c;即数据传输的速度。可以理解为家到公司中间马路的宽度 我们这里举个例子&#xff0c;计算机…

idea无法下载源码

1. 方式一 在项目下&#xff0c;项目根目录下 或 pom.xml同级目录中执行 mvn dependency:resolve -Dclassifiersources然后点击“download source”时就能看到源码了。

基于深度学习的视觉检测小项目(十二) 使用线条边框和渐变颜色美化界面

到目前为止&#xff0c;已经建立起了基本的项目架构&#xff0c;样式表体系也初步具备&#xff0c;但是与成品的界面相比&#xff0c;还是差点什么。 我的界面效果图&#xff1a; 优秀demo的界面截图&#xff1a; 是的&#xff0c;我的界面太“平” 了&#xff0c;没有立体感&…

【专题】2025年节日营销趋势洞察报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p38813 在当今复杂多变且竞争激烈的消费市场环境下&#xff0c;节日营销已成为企业获取市场份额、提升品牌影响力的关键战略时机。我们深知深入洞察节日营销趋势对于企业决策的重要性。 本报告汇总基于对 2024 年多个关键消费节点及…

【论文阅读】基于空间相关性与Stacking集成学习的风电功率预测方法

文章目录 摘要0. 引言1. 空间相关性分析2. 风电功率预测模型2.1 Stacking 集成策略2.2 基学习器2.2.1 基于机器学习算法的基学习器2.2.2 基于神经网络的基学习器2.2.3 基于粒子群优化算法的超参数优化 2.3 元学习器2.4 基于空间相关性与Stacking集成学习的风电功率预测方法 3 算…

Elasticsearch学习(2) :DSL和RestClient实现搜索文档

之前的学习中——Elasticsearch学习(1) &#xff0c;我们已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以这篇我们研究下elasticsearch的数据搜索功能。我们分别使用DSL(Domain Specif…

Mac 端 VSCode Flutter 快捷键大全

Mac 端 VSCode Flutter 快捷键大全 通用快捷键 基础导航 快捷键功能Cmd P快速打开文件Cmd Shift P打开命令面板Cmd T快速查找项目中的符号Cmd B显示/隐藏侧边栏Cmd Shift E聚焦到资源管理器Cmd \水平分屏 通用代码操作 快捷键功能Cmd Z撤销上一步操作Cmd Shift Z…

c#版本、.net版本、visual studio版本之间的对应关系

最近这几年一直没用过c#开发&#xff0c;都是从事Qt c开发工作&#xff0c;回想一下之前c#还要追溯到2019年&#xff0c;算算时间大概都已过去4&#xff0c;5年了&#xff0c;时间飞快。 2019真是个神奇的数字&#xff0c;vs2019是我用的时间最长的一个IDE&#xff0c;新冠起始…