目前初阶数据结构最难的一集-树+二叉树_堆

1.树的概念以及结构

1.1.树的概念

在数据结构中我们都是怎样定义树的?

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

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

其实很简单,为了更适合理解,我们引用出了树木+亲缘关系,结构上像倒过来的树,每个节点关系像父子关系,有点像家谱

节点的度:一个节点含有的子树的个数称为该节点的度; 就是看这个节点有几个孩子

叶节点或终端节点(也就是这棵树的最后):度为0的节点称为叶节点; 如上图:J、K、H、I...等节点为叶节点;就是看辈分最小(没有孩子的节点)的几个孩子

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度; 如上图:度最大的节点为A 树的度为3

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4(用家族的感觉就是这个家族有几代人)

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先,相当于直系亲属

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

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

子树之间不能相交,比如说BC不能相连,相连则不为树,

相连为另一种数据结构:图                

1.2.树的相关概念

树是递归定义的  任何一棵树都由【根和N(N>=0)个子树】构成

比如,A由A和3个子树(B,C,D)构成,而B又由2个子树(E,F)构成,就这样一层层的递归,最终形成一整个树

1.3.树的表示

总体来说,有三种表达方式

1.直接暴力c++

2.利用顺序表,存储指针数组

3.定义出孩子指针和兄弟指针,找出父节点的第一个左孩子,然后由这个左孩子找出他的兄弟们

1.4.树在实际生活中的应用

2.二叉树的概念以及结构

2.1.概念

一棵二叉树是结点的一个有限集合,该集合:

1. 或者为空

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

简单来说就是树最多只能分成俩叉

2.2.特殊的二叉树

1.满二叉树:满二叉树是一种特殊的二叉树,它的每个非叶子节点都有两个子节点,而且所有的叶子节点都在同一层上。换句话说,它是一棵深度为k且恰好有2^k-1个节点的二叉树,其中k为正整数。相当于每个父亲都有俩儿子,他也是一种特殊的完全二叉树

2.完全二叉树:完全二叉树是一种特殊的二叉树,它除了最后一层节点不满外,其余各层节点数都达到了最大值,并且最后一层的节点都集中在树的左侧。换句话说,如果把一棵完全二叉树的节点按照从上到下、从左到右的顺序依次编号,那么编号为i的节点的左儿子的编号一定为2i,右儿子的编号一定为2i+1,相当于之前都是俩儿子,到最后一辈开始参差不齐,但是从左到右必须连续,不能空

2.3.二叉树的性质

2.4.二叉树的存储结构

在我们想象中二叉树是像上面一样的,但是实际在计算机内存中却不是这么存储的

逻辑结构

物理结构

因此我们要把他想象成如下的结构

3.二叉树的顺序结构以及其实现

3.1.二叉树的顺序结构

相当于数组存储,因此我们在进行代码实现时必须代换成我们想象中的逻辑结构

我们假设上图中父亲为i,则左儿子则为2i+1,右儿子则为2i+2

假设儿子为i,则父亲为(i-1)/ 2,(因为计算机在进行整形除法时会删除小数点,所以不用区分左右儿子)

正式讲二叉树之前就让我们先看二叉树的衍生物--堆,来帮助我们更好理解

3.2.堆的概念及其结构  

首先堆必须是一个完全二叉树

其次通过父亲和孩子的大小关系可以区分为大小堆,大堆的根皆为最大,小堆同上

注意落实在内存中的存储不一定为升序 / 降序(因为兄弟之间没有大小关系)

升序:在数组中由下标0开始存储数据不断变大

降序:在数组中由下标0开始存储数据不断变小

3.3.堆的实现

3.3.1 堆的向下调整算法和向上排序算法
1.向下调整算法

为什么要用向下调整算法?

当我们进行想要删除堆的元素时往往需要删除堆的根节点,此时我们可能会想到直接将根节点取出来,但是堆并不是数组,如果盲目的将根节点删除,然后其他的补充位置的话,父子关系就会打乱,左右子节点将不会形成堆的结构,不利于调整

于是我们就用向下调整,先交换9和1,将1干到堆顶

然后与他的儿子比较比儿子小就下去,(谁大谁当爹)

代码如下:

特别注意:要使用向下调整算法左右子树都必须为大 / 小堆

2.向上调整算法

常用于堆的插入,知道儿子,找到爹,然后比,谁大谁当爹

3.3.2 堆的创建

首先我们直接说结论

要升序,建大堆

要降序,建小堆

那么就有人问为什么了?

别着急,小L来解答

首先假设我们建好了大堆然后想要进行排序   假设要排降序,此时最大值9已经在堆顶了  我们接下来要排次大,我们就只能将9先取出来,于是乎,就造成和向下调整算法一样的局面,父子关系直接乱套了,就要重新建堆,极大地增加了算法量,反观假如要进行升序我们直接使用向下调整算法,然后再将每次的最后一个孩子不管,于是就能拿到升序的结构,小堆同理

然后大家就会有疑问,为啥降序的时候不把堆顶和最后一个元素交换,然后用一样的思路?

其实不瞒大家,我之前也因为这个问题困惑很久,但是大家看,如果利用此思路,9就会调到

最后,调整完后8会到倒数第二,此时也还是升序啊!(在数组中由下标0开始存储数据不断变大)所以说,想要升序就要建大堆,想要降序就要建小堆

代码如下

有的铁铁会发现,我们建堆时使用的是向上调整算法,那么有没有其他算法来建堆呢?

答案是有的,向下调整算法也能用,这时候大家就会问,向下调整算法不是左右子树都必须为大 / 小堆吗?

俗话说的好,没条件我们也能创造条件,我们可以从底层开始做起,一步步实现,我们可以倒着调整,从倒数第一个父亲开始调整,将他实现成大堆,然后把他前面父亲也这么调,直到都为大堆

 

n-1是为了算出最后一个元素下标,再-1/2是为了算出他父亲

3.3.3 建堆的时间复杂度
1.向下调整算法O(N)

2.向上调整算法O(N*logN)

由上可得向下调整算法要优于向上调整算法

3.3.4 堆的插入

3.3.5 堆的删除
进行删除时需要将根节点和最后一个元素互相交换,再尾删最后一个,然后进行向下调整

3.3.6 堆的代码实现
Heap.h

Heap.c

3.4 堆的应用

3..1 Top-k问题

例如我的寝室长老王 王者是国服,游戏里的排行榜就是由此设计的,能利用堆排序来实现

  • 42
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值