目录
1.顺序结构
2.示意图
编辑
从物理结构还原为逻辑结构的方法
3.父子节点编号的规律
4.顺序存储的前提条件
5.堆的简介
堆的定义
小根堆和大根堆
6.堆的插入
7.堆的实现及操作堆的函数
堆的结构体定义
堆初始化函数HeapInit
堆插入元素函数HeapPush
堆向上调整函数AdjustUp
写法1
写法2
写法3
提问
测试堆插入函数
1.顺序结构
存储二叉树的两种结构:一种顺序结构,一种链式结构本文讲顺序结构
2.示意图
上方图中的数字0~6代表各个节点的编号
逻辑结构:方便人理解的结构 物理结构:实实在在存储的结构
可见顺序结构的底层是用数组(连续)存储的
从物理结构还原为逻辑结构的方法
对于满二叉树而言,
第一层有一个节点,第二层有两个节点,第一层有四个节点......
则可按层拆分
再组合
加上线
对于完全二叉树而言,做法和上述类似,不再赘述
3.父子节点编号的规律
比如求F的父节点,如果画图则太慢,其实可以看出规律
F编号为5,其父节点C编号为2;E编号为4,其父节点B编号为1;
发现(注:为高斯函数,又称向下取整函数)
★★★求父节点b编号的公式:
在C语言中为father = (child-1)/2;father获得表达式的商
如果给出父节点的编号,求左孩子和右孩子的编号
父节点C编号为2,则左孩子F的编号为2*2+1,则右孩子G的编号为2*2+2
★★★求孩子节点编号的公式:左孩子: 右孩子:
4.顺序存储的前提条件
结论:完全二叉树(含满二叉树)适合用顺序存储
如果非完全二叉树,存储会浪费空间
5.堆的简介
堆的定义
1.
2.
小根堆和大根堆
小根堆:树中所有的父节点的值都小于或等于孩子节点的值
大根堆:树中所有的父节点的值都大于或等于孩子节点的值
如果树中所有的父节点的值都等于孩子节点的值,则既为小根堆又为大根堆
注:等定义中并没有规定左孩子和右节点的值的大小关系,因此堆不一定有序
6.堆的插入
由堆的简介可知:堆是一个完全二叉树,因此可以用顺序结构实现
以下方大根堆为例
现要尾插数字20,由存储结构可以看出:空间不够,要扩容
插入20前,找其父节点(),发现后者值为30,可以插入,仍然满足大根堆的性质
再尾插入60
发现不满足大根堆的性质,需要一次调整
发现调整后仍然不满足大根堆的性质(56<60),需要再一次调整
发现调整后满足大根堆的性质(56<60<70),结束
上述的调整起名为向上调整,最多调整h(二叉树的高度)次
7.堆的实现及操作堆的函数
以大根堆为例
堆的结构体定义
可以用结构体来定义堆,由于堆的底层是用数组存储的,因此三个成员变量:数据域,大小size,容量capacity
写入头文件中
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
堆初始化函数HeapInit
要想使用堆必须先初始化堆(malloc,对size和capacity初始化值)
void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (php->a == NULL){perror("malloc");return;}php->size = 0;php->capacity = 4;
}
php->capacity跟随malloc函数开辟空间的大小
堆插入元素函数HeapPush
插入前先判断空间是否充足,不够则relloc原来的2倍.之后调用向上调整函数进行插入
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);if (tmp == NULL){perror("realloc");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;//插入至数组的最后一个元素的下一个位置(size)php->size++;//数组大小+1//调用向上调整函数AdjustUp(php->a,php->size-1);
}
注意到AdjustUp传的第二个参数是php->size-1
堆向上调整函数AdjustUp
如果a[parent] < a[child],则进行交换,之后调整parent和child的值,以便于下一次调整
写法1
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (a[parent] < a[child]){Swap(&a[parent], &a[child]);//调整parent和child的值child = parent;parent = (parent - 1) / 2;}
}
写法2
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (parent>=0){if (a[child] > a[parent]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
写法3
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] > a[parent]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
提问
已知这几种写法都能成功运行,哪个写法存在不规范的地方?
答:从特殊情况考虑问题,写法2:
当parent==0时,进入循环,若child==1,if判断成立,交换值后,child==0,parent==-1/2==0(取商)
while(parent>=0)条件仍然成立,但不满足if (a[child] > a[parent]),因此break
不规范的地方:child==parent==0就没有必要再次进入循环,建议改成while (child>0)(写法3)
测试堆插入函数
main.c写入以下代码,下断点至return 0;
#include "Heap.h"
int main()
{HP hp;HeapInit(&hp);HeapPush(&hp, 1);HeapPush(&hp, 3);HeapPush(&hp, 0);HeapPush(&hp, 5);HeapPush(&hp, 8);HeapPush(&hp, 12);HeapPush(&hp, 2);HeapPush(&hp, 5);HeapPush(&hp, 30);HeapPush(&hp, 50);return 0;}
打开监视窗口查看
画图后发现符合大根堆的性质