引言
在计算机科学中,二叉树是一种重要的数据结构,广泛应用于各种算法和程序设计中。本文将探讨二叉树的顺序实现,特别是堆的实现方式。
一、树
1.1树的概念与结构
树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做
树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。
• 有⼀个特殊的结点,称为根结点,根结点没有前驱结点。 • 除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、……、Tm ,其中每⼀个集合 Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继。因此,树是递归定义的。
树形结构中,⼦树之间不能有交集,否则就不是树形结构
⾮树形结构:
总结: • ⼦树是不相交的(如果存在相交就是图了,图以后得课程会有讲解) • 除了根结点外,每个结点有且仅有⼀个⽗结点 • ⼀棵N个结点的树有N-1条边
1.2树的基本术语
- ⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:A是B的⽗结点
- ⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图:B是A的孩⼦结点
- 结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0
- 树的度:⼀棵树中,最⼤的结点的度称为树的度; 如上图:树的度为 6
- 叶⼦结点/终端结点:度为 0 的结点称为叶结点; 如上图: B、C、H、I... 等结点为叶结点
- 分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: D、E、F、G... 等结点为分⽀结点
- 兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟); 如上图: B、C 是兄弟结点
- 结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;
- 树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为 4
- 结点的祖先:从根到该结点所经分⽀上的所有结点;如上图: A 是所有结点的祖先
- 路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为: A-E-J-Q;H到Q的路径H-D-A-E-J-Q
- ⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙
- 森林:由 m(m>0)棵互不相交的树的集合称为森林;
1.3树的表示法
孩⼦兄弟表⽰法:
树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦兄弟表⽰法等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法
代码语言:javascript复制struct TreeNode
{
struct Node* child; // 最左边的孩子结点
struct Node* brother; // 指向其右边的下一个兄弟结点
int data; // 结点中的数据域
};
1.4树形结构实际运⽤场景
⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。
二、二叉树
2.1二叉树的概念与结构
二叉树(binary tree) 是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二” 的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。
总结: 1. ⼆叉树不存在度⼤于 2 的结点 2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树 注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的
2.2二叉树的基本术语
- 根节点(root node) :位于二叉树顶层的节点,没有父节点。
- 叶节点(leaf node):没有子节点的节点,其两个指针均指向 None 。
- 节点所在的 层(level) :从顶至底递增,根节点所在层为 1 。
- 节点的度(degree) :节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
- 二叉树的高度(height) :从根节点到最远叶节点所经过的边的数量。
- 节点的深度(depth) :从根节点到该节点所经过的边的数量。
- 节点的高度(height) :从距离该节点最远的叶节点到该节点所经过的边的数量。
2.3特殊二叉树
1.完美二叉树(满二叉树)
完美二叉树(perfect binary tree) 所有层的节点都被完全填满。在完美二叉树中,叶节点的度
为 0 ,其余所有节点的度都为 2 ;若树的高度为 ℎ ,则节点总数为 2 ℎ 1 − 1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。
2. 完全二叉树
完全二叉树(complete binary tree) 只有最底层的节点未被填满,且最底层节点尽量靠左填充。
2.4二叉树的存储结构
⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。
2.4.1顺序结构
顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树 ,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。
2.3.2 链式结构
⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 。链式结构⼜分为 ⼆叉链 和 三叉链 。(红⿊树等会⽤到三叉链。)
三、实现顺序结构二叉树
⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备其他的特性。
3.1堆的概念与结构
堆是一种完全二叉树,具有以下性质:
- 最大堆:每个节点的值都大于或等于其子节点的值。
- 最小堆:每个节点的值都小于或等于其子节点的值。
堆常用于实现优先队列,因为它能有效地支持插入和删除操作。
总结 • 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值; • 堆总是⼀棵完全⼆叉树。
3.2堆的实现
我们以小堆为例进行实现:
3.2.1存储结构
二叉树的顺序存储通常使用数组来实现。对于一个节点在数组中的索引
i
,可以通过以下方式找到其父节点和子节点的索引:
- 父节点:
(i - 1) / 2
(取整) - 左子节点:
2 * i 1
- 右子节点:
2 * i 2
typedef struct Heap
{
DataType *arr;
int size;
int capacity;
}HP;
这里可以看到堆的底层结构和动态顺序表一样。
3.2.2相关操作
1.初始化
代码语言:javascript复制//初始化
void HPInit(HP* p)
{
assert(p);
p->arr = NULL;
p->size = p->capacity = 0;
}
2.销毁
代码语言:javascript复制//销毁
void HPDestory(HP* p)
{
assert(p);
if (p->arr)
{
free(p->arr);
}
p->arr = NULL;
p->size = p->capacity = 0;
}
Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
3.向上调整