二叉树
1. 树
1.1 树的概念
逻辑结构:想像出来的
物理结构:在内存中的存储的结构
树是一种非线性的数据结构,是由多个节点组成的一个具有层次关系的集合。
树的特点:
- 树中有一个特殊的节点,称为根节点,根节点没有前驱节点。
- 除根结点之外,其余的节点和其下的子节点有组成互不相交的集合,每一个互不相交的集合又可以称为子树,并且只有一个前驱节点,可以有多个后继节点。
- 树是递归定义的。
注意: 树中的每一棵子树一定是互不相交的,否则就不是树形结构
1.2 树的相关概念(重要)
- 节点的度: 一个节点含有的子节点的个数,称为该节点度。
- 叶子节点: 度为0的节点称为叶子节点。
- 分支节点: 度不为0的节点。
- 父节点: 若一个节点有后继节点,这个节点就称为后继节点的父节点。
- 兄弟节点:拥有同一个前驱节点互称为兄弟节点。
- 树的度: 一棵树中最大节点的度称为树的度。
- 节点的层次: 从根节点定义为第一层。
- 树的深度或这高度: 树中的节点的最大层次。
1.3 树的表达方式
树的结构比较复杂即要保存数值,又要保存节点与节点之间的关系。
树最常用的方法为左孩子右兄弟表示法
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
2. 二叉树
2.1 二叉树的概念
二叉树是节点的一个有限集合: 由一个根节点加上左子树和右子树的二叉树组成。
注意:1. 二叉树不存在度大于2的节点。 2. 二叉树的子树分为左右之分,顺序不能颠倒,因此二叉树是有序树。
2.2 特殊二叉树
1.满二叉树:一个二叉树,如果每一层的节点树达到最大值,则为满二叉树。
若满二叉树的有h层,则节点个数为2h - 1 。
2.完全二叉树:前面h层必须是满的,最后一层不一定满,但是从左到右必须连续 。
若完全二叉树有h层 ,则有节点个数在区间 [ 2h-1 , 2h -1 ]之间。
2.3 二叉树的性质
- 若根节点的层数为1 ,则一颗非空的二叉树的第 i 层上最多有 2^( i -1 )^ 个节点。
- 若根节点的层数为1 , 则深度为h的二叉树的最大节点树为2h - 1。
- 任意的二叉树,如果度为0的叶子节点个数为n1 ,度为2的分支节点,则n1 = n2 + 1 .
- 若根节点的层数为1, 具有n个节点的满二叉树,其深度为h = log(n+1)(以2为底,n+1 为对数).
2.4 二叉树的存储结构
1.顺序结构
一层一层存到数组中
父子节点间下标有一个规律关系
leftchild = parent * 2+1
rightchild = parent * 2+2
parent = (child-1)/2
顺序结构就是用数组来存储,使用数组存储只适用表示完全二叉树。二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树。
2.链式结构
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
}
3.二叉树的顺序结构(堆)
一般的二叉树的不适用数组来储存的,因为可能造成空间浪费。而完全二叉树适合用顺序结构来储存。堆(一种完全二叉树)常常使用顺序结构来储存。要注意: 这里的堆要和操作系统种的堆要区分开,一个是数据结构,一个是内存的区域。
3.1 堆的概念
堆是以二叉树的顺序结构来存储的,堆分为大堆和小堆。大堆要满足堆中的某个节点的值总是小于父节点,小堆则相反小堆中的某个节点的值总是大于其父节点。
堆的性质:
- 堆总是一颗完全二叉树
- 堆中节点的值不一定是有序的
3.2 堆的实现
3.2.1 堆的插入
堆是由顺序结构来表达的,在堆中插入节点就不仅仅值只是在物理结构上插入数据,也要符合堆的逻辑结构的规则。以小堆为例,先在数组的最后插入数据后再与前面的数据进行比较,若是小于前驱节点,就要于前驱节点的值交换,直到符合小堆的规则为止。
向上调整:
void AdjustUp(HpDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
3.2.2 堆的删除(删除堆顶数据)
堆的物理结构是数组,但是逻辑结构并不是数组。所以堆的删除并不是简单的删除数组中的数据而已。在这里我们就要用到向下调整算法了。还是以小堆为例子,先将堆顶的数据于数组中最后一个数据交换,再删除数组中的最后一个数据。现在因为数组中的数据已经不构成堆了,因此我们要使用向下调整的方法调整数组中的数据使其形成一个新的小堆。(注意: 不管是向下调整还是向上调整,使用的前提是除了堆顶的数据外左右子树都是堆。)
void AdjustDown(HpDataType* a, int parent,int size)
{
int child = parent * 2 + 1;
while (child<size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}
3.2.3 计算建堆的使时间复杂度
因此:建堆的时间复杂度为O(N)。