上次介绍了树,二叉树的基本概念结构及性质
今天带来的是:二叉树顺序结构与堆的概念及性质,还会用c语言来实现堆
1. 二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。完全二叉树就比较适合使用顺序结构存储(数组)。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储
注意:此堆非“彼堆”——操作系统虚拟进程地址空间中的堆。二者一个是一个是数据结构,一个是操作系统中管理内存的一块区域
2.堆的概念和结构
堆需要满足两点:
堆是一个完全二叉树,即除了最底层,其他层都是完全填满,最底层从左到右填充
堆中的每个节点的值都必须大于等于(最大堆)或小于等于(最小堆)其子节点的值
根据节点值的大小关系,堆可以分为最大堆和最小堆。在最大堆中,根节点的值最大,每个节点的值都大于等于其子节点的值。在最小堆中,根节点的值最小,每个节点的值都小于等于其子节点的值
3.堆的实现(小堆)
3.1项目文件规划
- 头文件Heap.h:用来基础准备(常量定义,typedef),链表表的基本框架,函数的声明
- 源文件Heap.c:用来各种功能函数的具体实现
- 源文件test.c:用来测试功能是否有问题,进行基本功能的使用
3.2结构体和各功能一览(Heap.h)
typedef int HPDataType; typedef struct Heap//用顺序表来实现,跟顺序表的结构一样 { HPDataType* a; int size;//数量 int capacity;//容量 }HP; void HeapInit(HP* php);//初始化 void HeapDestroy(HP* php);//销毁 void HeapPush(HP* php, HPDataType x);//插入 // 规定删除堆顶(根节点) void HeapPop(HP* php);//删除 HPDataType HeapTop(HP* php);//返回根(堆顶)的存储的数据 int HeapSize(HP* php);//堆的数据个数 bool HeapEmpty(HP* php);//是否为空
3.3重要函数详解(Heap.c)
3.3.1堆向上调整算法
i位置节点的双亲序号:(i-1)/2
void Swap(HPDataType* p1, HPDataType* p2) { HPDataType tmp = *p1; *p1 = *p2; *p2 = tmp; } void AdjustUp(HPDataType* a, int child)//传入数组和下标索引 { int father = (child - 1) / 2;//利用公式来算出父亲节点下标 while (child > 0) { if (a[child] < a[father]) { Swap(&a[child], &a[father]); //更新下标 child = father; father = (father - 1) / 2; } else { break;//一旦符合小堆了,就直接退出 } } }
Swap
函数用于交换两个指针指向的值,而 AdjustUp
函数用于通过比较子节点与父节点并在有必要时交换它们来调整堆的结构,然后向上移动树,直到满足堆的性质
3.3.2堆向下调整算法
i位置的左孩子是2 ∗ i + 1 2*i+12∗i+1,右孩子2 ∗ i + 2 2*i+22∗i+2
void AdjustDown(HPDataType* a, int n, int father) { int child = father * 2 + 1;//假设左孩子小 找出两者较小的来跟父节点比(大堆就找二者中较大的了) while (child < n) { if (child + 1 < n && a[child] > a[child + 1]) { child++; } if (a[child] < a[father]) { Swap(&a[child], &a[father]); father = child; child = father * 2 + 1; } else { break; } } }
给定一个数组 a,表示堆的结构,以及数组的大小 n 和要进行调整的父节点的索引 father
计算父节点的左孩子的索引为 father * 2 + 1
进入一个 while 循环,只要左孩子的索引小于 n (不会出数组)就会继续
在循环内部,首先检查右孩子是否存在且右孩子的值是否大于左孩子的值,如果是,则更新 child 为右孩子的索引。这是为了找出左右孩子中值较大的那个
比较左孩子的值和父节点的值,如果左孩子的值小于父节点的值,则调用 Swap 函数交换这两个索引处的值,并更新 father 为 child 的值,然后重新计算 child 的索引。这一步的目的是将较大的子节点值向上移动,以满足堆的性质
如果左孩子的值不小于父节点的值,则跳出循环,因为堆的性质已经满足
3.4各功能实现(Heap.c)
初始化和销毁
void HeapInit(HP* php) { assert(php); php->a = NULL; php->size = php->capacity = 0; } void HeapDestroy(HP* php) { assert(php); free(php->a); php->a = NULL; php->size = php->capacity = 0; }
插入
void HeapPush(HP* php, HPDataType x) { assert(php); if (php->size == php->capacity)//检查有没有满 { int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2; HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType)); if (tmp == NULL) { perror("realloc fail"); return -1; } php->a = tmp; php->capacity = newCapacity; } //开始插入 php->a[php->size] = x; php->size++; //要确保是小堆 AdjustUp(php->a, php->size - 1); }
删除堆顶
void HeapPop(HP* php)//最外面那个和堆顶交换后,删去最外面的,再把新堆顶向下调整成小堆 { assert(php); assert(php->size > 0);//保证有东西可以删 Swap(php->a[0], php->a[php->size - 1]); php->size--; AdjustDown(php->a, php->size, 0); }
返回根(堆顶)的存储的数据
HPDataType HeapTop(HP* php) { assert(php); assert(php->size > 0); return php->a[0]; }
节点数量
int HeapSize(HP* php) { assert(php); return php->size; }
是否为空
bool HeapEmpty(HP* php) { assert(php); return php->size == 0; }
3.5建堆时间复杂度
建堆的时间复杂度为O(N)