⭐️ 往期相关文章
✨链接1:【数据结构】顺序表
✨链接2:【数据结构】单链表
✨链接3:【数据结构】双向带头循环链表
✨链接4:【数据结构】栈和队列
⭐️ 树的概念
百度百科的解释:树是一种非线性的数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
- 有一个特殊的结点,称为根节点(root),根结点没有前驱结点。
- 除根节点外,其余结点被分成 M ( M > 0 ) M(M > 0) M(M>0)个互不相交的集合,其中每一个集合又是一颗结构与树类似的子树,每颗子树的根结点有且仅有一个前驱结点,可以有0个或者多个后继结点。
注意:
树型结构中,子树之间不能有交集,否则就不是树型结构。
- 节点的度: 一个节点含有的子树的个数称为该节点的度。如上图:A的度为6
- 叶子节点或终端节点: 度为0的节点称为叶子节点。如上图:B、C、H、I、P、O、K…等节点都是叶子节点
- 非终端结点或分支结点: 度不为0的节点
- 父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点
- 子节点: 一个节点含有的子树的根节点称为该节点的子节点。如上图:B是A的子节点
- 兄弟节点: 具有相同父节点的节点互称兄弟节点
- 树的度: 一棵树中,最大节点的度称为树的度
- 树的高度或深度: 如上图:树的高度为4
- 堂兄弟节点: 父节点在同一层的节点互为堂兄弟
- 祖先节点: 从根到该节点所经分支上的所有结点都叫做该结点的祖先结点(父节点也是祖先节点)
- 子孙节点: 以某节点为根的子树中任一节点都成为该节点的子孙
- 森林: 又多棵树且互不相交的结合称为森林
⭐️ 二叉树的概念
以上树的概念都适用于二叉树,但是二叉树在树的基础上添加了一些条件。
- 二叉树不存在度大于2的节点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
⭕️ 特殊的二叉树
- 满二叉树: 一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树,也就是说,如果一个二叉树的层数为 k k k,且节点总数是 2 k − 1 2^k - 1 2k−1,则就是满二叉树。
- 完全二叉树: 一个二叉树,假设树的深度是 k k k,前 k − 1 k - 1 k−1 层节点是满的,最后一层节点从左到右连续且最少有一个,当最后一层也是满的话,这样应该被称为满二叉树。完全二叉树是一种效率很高的数据结构。注意:满二叉树是一种特殊的完全二叉树。
⭕️ 二叉树的性质
- 若规定根节点的层数为1,则一颗非空二叉树的第 h h h 层上最多有 2 ( h − 1 ) 2^{(h-1)} 2(h−1) 个节点。
- 若规定根节点的层数为1,则深度为 h h h 的二叉树的最大节点数是 2 h − 1 2^h - 1 2h−1。
- 对于任意一颗二叉树,如果度为 0 0 0 也就是其叶子节点个数为 x 0 x_0 x0,度为 2 2 2 的分支节点个数为 x 2 x_2 x2,则有 x 0 = x 2 + 1 x_0 = x_2 + 1 x0=x2+1 或 x 2 = x 0 − 1 x_2 = x_0 - 1 x2=x0−1。
- 若规定根节点的层数为1,具有 n n n 个节点的满二叉树的深度, h = l o g 2 ( n + 1 ) h = log_2(n+ 1) h=log2(n+1)
- 对于具有
n
n
n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始。则对于序号为
i
i
i 的节点有:
- 若 i > 0 i > 0 i>0, i i i 位置节点的父节点序号: ( i − 1 ) / 2 (i - 1) / 2 (i−1)/2。 i = 0 i = 0 i=0,根节点无父节点。
- 若 2 ∗ i < n 2*i < n 2∗i<n 左孩子下标: 2 ∗ i + 1 。 2 * i + 1。 2∗i+1。 ( n n n 为数组长度)
- 若 2 ∗ i < n 2*i < n 2∗i<n 右孩子下标: 2 ∗ i + 2 。 2 * i + 2。 2∗i+2。 ( n n n 为数组长度)
⭐️ 堆的概念
百度百科的解释:堆(Heap)是计算机科学中一类特殊的数据结构,是最高效的优先级队列。堆通常是一个可以被看作一棵完全二叉树的数组对象。
- 堆中某个结点的值总是不大于或不小于其父结点的值。
- 堆总是一棵完全二叉树。
小根堆: 每一个父节点的值总是小于等于子节点。
大根堆: 每一个父节点的值总是大于等于子节点。
注:
堆在物理存储结构上数组的形式,在逻辑结构上是二叉树。
⭕️ 堆的核心算法 (向上调整算法与向下调整算法)
AdjustUp
实现思路: 向上调整算法通常在堆 push
的时候使用向上调整算法来继续维护当前是一个堆的结构。当堆添加数据的时候,如果是小根堆要满足父节点的数据要小于等于子节点的数据,大根堆同理。其实堆在 push
数据的时候,不会影响到兄弟节点,只会影响到其父节点,所以通过二叉树的性质来计算出父节点在与当前子节点比较,若子节点小于父节点则交换。
AdjustUp
实现(小根堆为例):
// 向上调整
void AdjustUp(HeapDataType* data, int child) {
assert(data);
// 计算父节点位置
int parentNode = (child - 1) / 2;
while (child > 0) {
if (data[child] < data[parentNode]) {
// 交换
Swap(&data[child] , &data[parentNode]);
// 继续向上搜索
child = parentNode;
parentNode = (child - 1) / 2;
}
else {
break;
}
}
}
AdjustDown
实现思路:向下调整算法通常在 pop
堆顶数据后来继续维护当前是一个堆的结构,这里采用了一个很巧妙的方法把堆顶元素与最后一个下标元素进行交换,而堆在物理上又是一个顺序表只需要 --size
即可删除最后一个元素,在通过向下调整算法也就是将下标为 0
的元素 ( parent节点
) 和左右孩子中较小的那个进行比较,若较小的孩子要小于当前的 parent
节点,则交换。
AdjustUp
实现(小根堆为例):
void AdjustDown(HeapDataType* data, int size, int parent) {
assert(data);
// 默认左孩子最小
int child = parent * 2 + 1;
while (child < size) {
// 判断左孩子和右孩子谁更小
// 若右孩子小则改变child 右孩子不能越界
if ( (child + 1 < size) && (data[child + 1] < data[child]) ) {
child++;
}
// 最小的孩子是否比父节点小
if (data[child] < data[parent]) {
// 交换
Swap(&data[child] , &data[parent]);
// 迭代
parent = child;
child = parent * 2 + 1;
}
else {
// 父节点 <= 最小的孩子节点
break;
}
}
}
⭕️ 堆的实现
堆的结构定义与接口
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HeapDataType;
// 逻辑结构 完全二叉树
// 内存结构 顺序表
typedef struct Heap {
HeapDataType* data;
int size;
int capacity;
}Heap;
void AdjustDown(HeapDataType* data, int size, int parent);
void AdjustUp(HeapDataType* data, int child);
void HeapPrint(Heap* hp);
void Swap(HeapDataType* value1 , HeapDataType* value2);
void HeapInit(Heap * hp);
void HeapDestroy(Heap * hp);
void HeapPush(Heap * hp , HeapDataType value);
void HeapPop(Heap * hp);
HeapDataType HeapTop(Heap * hp);
bool HeapIsEmpty(Heap* hp);
int HeapSize(Heap * hp);
HeapInit
实现:
void HeapInit(Heap* hp) {
assert(hp);
hp->data = NULL;
hp->size = 0;
hp->capacity = 0;
}
HeapDestroy
实现:
void HeapDestroy(Heap* hp) {
assert(hp);
free(hp->data);
hp->capacity = hp->size = 0;
}
HeapPrint
实现:
void HeapPrint(Heap* hp) {
assert(hp);
for (int i = 0; i < hp->size; i++) {
printf("%d " , hp->data[i]);
}
printf("\n");
}
HeapPush
实现:
void HeapPush(Heap* hp, HeapDataType value) {
assert(hp);
// 检查容量
if (hp->size == hp->capacity) {
int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HeapDataType* newData = (HeapDataType*)realloc(hp->data , sizeof(HeapDataType) * newCapacity);
assert(newData);
hp->data = newData;
hp->capacity = newCapacity;
}
hp->data[hp->size] = value;
hp->size++;
// 向上调整
AdjustUp(hp->data , hp->size - 1);
}
HeapPop
实现:
// 删除堆顶的数据 堆顶选数小堆最小数
void HeapPop(Heap* hp) {
assert(hp);
// 堆不为空
assert(!HeapIsEmpty(hp));
// 交换
Swap(&hp->data[0] , &hp->data[hp->size - 1]);
hp->size--;
// 向下调整
AdjustDown(hp->data , hp->size , 0);
}
HeapTop
实现:
HeapDataType HeapTop(Heap* hp) {
assert(hp);
// 堆不为空
assert(!HeapIsEmpty(hp));
return hp->data[0];
}
HeapIsEmpty
实现:
bool HeapIsEmpty(Heap* hp) {
assert(hp);
return hp->size == 0;
}
HeapSize
实现:
int HeapSize(Heap* hp) {
assert(hp);
return hp->size;
}
Swap
实现:
void Swap(HeapDataType* value1, HeapDataType* value2) {
assert(value1 && value2);
HeapDataType temp = *value1;
*value1 = *value2;
*value2 = temp;
}
注:
小根堆还是大根堆是由 AdjustUp
和 AdjustDown
来控制的。