介绍树的概念,二叉树相关结构计算以及堆。
一.树
树结构具有明显的层次特性,如同家族谱系一样。一般用“父子”来称呼体现节点之间的层次差异和继承关系。父节点处于较高层次,是子节点的“先辈”,子节点则依赖父节点存在,如同现实中子女与父母的关系,便于描述和理解树中节点的上下层级关系。
1.概念:
任何一棵树都是由根和子树构成的,子树没有了树就结束了。
基本概念总结:
①节点的度
节点拥有子树数目称为节点的度。如,A有3个子节点,那么它的度就是3。
②树的度:
树中节点的最大度数称为树的度。度为0的节点称为叶子节点,也叫终端节点。
④内部节点/非终端节点
度不为0的节点称为内部节点。反之度为0称叶节点或终端节点
⑤父节点、子节点和兄弟节点
一个节点的子树的根节点是该节点的子节点,而这个节点就是其子节点的父节点。具有相同父节点的节点互为兄弟节点。
⑥树的深度/高度
树中节点的最大层次称为树的深度或。根节点在第1层,若某节点在第i层,那么它的子节点在第i + 1层。
⑦森林
森林是m(m≥0)棵互不相交的树的集合。可以把树的根节点删去就得到森林。
⑧ 有序树和无序树
如果树中节点的子树是有顺序的,即子树之间有先后次序之分,则称该树为有序树;否则称为无序树
注意:树的子树是不相交的,除了根节点,每个节点有且只有一个父节点,一棵N个节点的树就有N-1条边。
2.左孩子右兄弟表示法:
左孩子右兄弟表示法是一种用于表示树的数据结构的链式方法,树的每个节点除了存储自身的数据外,还包含两个指针,分别指向该节点的第一个孩子节点(左孩子)和下一个兄弟节点(右兄弟)。通过这种方式,就能够找全树的所有节点。
二.二叉树
1.概念:
(1)二叉树最大度是2 (2)二叉树有左右子树之分,次序不能颠倒(有序树)
简单理解,二叉树是一棵计划生育的树,每个节点最多只能有两个子节点。
2.两个特殊二叉树:
(1)满二叉树:每一层都是满的。
注:在树结构中,根节点所在的层为第0层。从根节点开始,向下延伸一层,根节点的子节点所在的层为第1层。以此类推,每向下延伸一层,层数就加1。(这样设计符合计算机习惯(如数组索引从0开始),便于描述、计算树的性质,实现递归。)
(2)完全二叉树:除了最后一层,每一层都是满的。
二叉树只有度0,度1,度2(1个子树,2个子树,无子树),分别用N0,N1,N2表示。那么在完全二叉树中有一下结论:N1=0或1,N0 = N2+1;
由此,如果已知节点数,就可求N0,N1,N2。
3.parent和child关系:
那么在顺序存储中如何连接父子关系?
顺序(数组)方式:适合对满二叉树/完全二叉树存储,若不是完全二叉树,数组中会间隔很多“空”出来位置,浪费空间。
三.堆
1.基本概念:
堆是一种特殊的数据结构,通常用于实现优先队列。
- 近似完全二叉树结构:堆可以被看作是一棵近似完全二叉树。除了最底层,其他层的节点都是满的,并且最底层的节点都尽量靠左排列 。
- 堆序性质
- 最大堆(大根堆、大顶堆) :在大堆中,任意节点的值都大于或等于其子节点的值。这意味着堆顶(根节点)元素是整个堆中的最大值。
- 最小堆(小根堆、小顶堆) :在小堆中,任意节点的值都小于或等于其子节点的值。即堆顶元素是整个堆中的最小值。
2.建堆:
当我们有10个数据,该如何让它以二叉树的形式进行操作?
(1)向下调整建堆:
即不断调整父子节点顺序,让值大的作为父节点,值小的作为子节点。但如果我们从上往下调整,比如从1开始不断调整父子节点:
调整完第三次我们就会发现,值为9的节点并没有被调整到合适的位置?难道我们要再从根节点8调整一遍?每一次调整完都仍需要不断重复调整根节点?
这当然是不正确的。
向下调整建堆的前提条件是“下”部分已经是一个堆,那么就我们需要从最后一棵子树开始调整,让下面的部分先形成堆。
先找到最后一个节点的父节点,再不断向前,直到整棵树的根节点:
最终结果满足任意子节点值小于父节点:建堆完成。
有了逻辑开始实现代码(仅以int类型10个元素为例):
(2)向上调整建堆
与向下调整建堆通过将节点向下调整形成堆一致,我们要将节点不断向上调整完成建堆过程。同样要满足:调整时“上部分”已经是堆,那么我们需要从上开始调整。
对之后的不满足 父>子 的节点继续操作,直到最后一个节点结束。
如最右侧图所示:通过向上调整建堆得到了一个最大堆。
但是我们观察到,即使这与向下调整建堆得到的堆并不完全一致,但都有任意父节点 > 子节点。由此可以得出:不同建堆方法得到的堆可能不一致,但都它们都是堆(类似导数的原函数不唯一)。
代码实现:
总结:
1.调整方向
- 向上调整建堆:从最上的叶子节点开始,将每个节点与其父节点比较交换,直到根节点或满足堆的性质。
- 向下调整建堆:从最下的根节点开始,与它的左右子节点比较交换,不断向下调整,直到叶子节点或满足堆的性质。
2.时间复杂度(向下调整建堆更优)
-向上:O(N*logN)
-向下:O(N)
原因:二叉树最后一层占了几乎一半节点,从下向上调整节点数少,次数少,而从上向下,最后每个节点都要处理,调整次数多(不具体证明了)
3.适用场景
- 向上调整建堆:适合在不断插入元素的过程中构建堆,例如在优先队列中,每次插入一个元素后,可以通过向上调整来维护堆的性质。
- 向下调整建堆:适合在已经有一个无序数组的情况下构建堆,通过从最后一个非叶子节点开始向下调整,可以快速将数组调整为堆。
3.堆的插入删除逻辑:
由于堆的结构,当我们插入删除时需要考虑:删哪个?怎么删?删完怎么调整?怎么调整插入数据?
(1)删除:
尾删不用过多考虑,而头删呢?在数组中直接挪动数据吗?这不可取:效率低下,且整个树的父子关系全乱了。那我们要不断找出子节点,选出大的那个往上挪动?理论可行,但与其这样,我们可以考虑一种更直接的方法:将根节点和最后一个节点交换,,让整个数组大小减1,在通过向下调整建堆将其调整到合适位置(首尾交换,向下调整)
这样,只需要处理一个元素,就能高效的删除元素,得到新的堆。
(2)插入:
相比较删除,插入简单的多,有了向上调整的逻辑,只需要将数据尾插,之后利用向上调整第一个节点即可。