一.前言
前面我们讲解了二叉树的概念以及二叉树的存储结构:https://blog.csdn.net/yiqingaa/article/details/139224974?spm=1001.2014.3001.5502
今天我们主要讲讲二叉树的存储结构,以及堆的实现。
二.正文
1.二叉树的顺序结构及实现
1.1二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.2堆的概念及结构
如果有一个关键码的集合K = { k₀,k₁,k₂ ,...,k_(n-1)(注意:_()在这里表示()里是下标 ,如我们这里表示的是k的下标是n-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:K_(i) <=K_(2*i+1) 且K_(i) <=K_(2*i+2) (K_(i) >=K_(2*i+1) 且 K_(i)>=K_(2*i+2) ) i = 0,1,2...,则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值。
- 堆总是一棵完全二叉树。
值得注意的是:这里我们虽然把它想像成树的形状,但其实我们的底层是数组,我们是通过改变数组,来控制这棵"树"的。
打个比方来说:蚂蚁上树这道菜,这盘菜里面真的有蚂蚁吗?其实没有,只不过是人为想像成的而已。在这里的二叉树也是同样的道理。
2.堆的实现
2.1堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
这里我们给了一个数组。
cppint array[] = {27,15,19,18,28,34,65,49,25,37};
cpp//向下调整算法 void AdjustDown(HPDataType* php,int n ,int parent )//php是数组指针 { int child = parent * 2 + 1; if (php[child] > php[child + 1]) child = parent * 2 + 2; while (child < n) { if (php[child] < php[parent]) { Swap(&(php[child]), &(php[parent])); parent = child; child = parent * 2 + 1; } else { break; } } }
2.2向上调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向上调整算法可以把它调整成一个小堆。
我们给出了一个数组:
cppint array[] = {15,18,19,25,28,34,65,49,27,37,2};
cppvoid AdjustUp(HPDataType* php,int child)//向上调整算法 { int parent = (child - 1) / 2; while (child > 0) { if (php[child] < php[parent]) { Swap(&(php[child]), &(php[parent])); child = parent; parent = (child - 1) / 2; } else { break; } } }
2.3堆的插入
先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
cppvoid HPPush(HP* ps,HPDataType x)//堆尾插入数据,ps是我们堆指针 { assert(ps); if (ps->size == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4: ps->capacity * 2; HPDataType* arr = (HPDataType*)realloc(ps->a,sizeof(HPDataType) * newcapacity); if (arr == NULL) { perror("realloc false"); return; } ps->a = arr; ps->capacity = newcapacity; } ps->a[ps->size] = x; ps->size++; AdjustUp(ps->a,ps->size-1); }
2.4堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
cppvoid HPPop(HP* ps)//删除堆顶数据 { assert(ps); assert(ps->size > 0); Swap(&(ps->a[0]), &(ps->a[ps->size - 1])); ps->size--; AdjustDown(ps->a,ps->size,0); }
3.堆代码的实现
Heap.h
cpp#pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int HPDataType; struct Heap { HPDataType* a; int size; int capacity; }; typedef struct Heap HP; void HPInit(HP* ps);//堆的初始化 void HPDestroy(HP* ps);//堆的删除 void HPPush(HP* ps, HPDataType x);//堆尾插入数据 void HPPop(HP* ps);//删除堆顶数据 HPDataType HPTop(HP* ps);//取出堆顶数据 bool HPEmpty(HP* ps);//判断堆是否为空 void Swap(HPDataType* a, HPDataType* b);//交换两个数据 void AdjustUp(HPDataType* php, int child);//向上调整算法 void AdjustDown(HPDataType* php, int n, int parent);//向下调整算法 int HPSize(HP* ps);//返回堆的有效数据个数 void HPSort(HPDataType* php, int n);//堆排序
Heap.c
cpp#include"Heap.h" void HPInit(HP* ps)//堆初始化实现 { assert(ps); ps->a = NULL; ps->size = ps->capacity = 0; } void HPDestroy(HP* ps)//堆销毁实现 { assert(ps); free(ps->a); ps->a = NULL; ps->size = ps->capacity = 0; } void Swap(HPDataType* a,HPDataType* b)//两个数据的交换 { HPDataType tmp =*a; *a = *b; *b = tmp; } void AdjustUp(HPDataType* php,int child)//向上调整算法 { int parent = (child - 1) / 2; while (child > 0) { if (php[child] < php[parent]) { Swap(&(php[child]), &(php[parent])); child = parent; parent = (child - 1) / 2; } else { break; } } } void HPPush(HP* ps,HPDataType x)//堆尾插入数据 { assert(ps); if (ps->size == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4: ps->capacity * 2; HPDataType* arr = (HPDataType*)realloc(ps->a,sizeof(HPDataType) * newcapacity); if (arr == NULL) { perror("realloc false"); return; } ps->a = arr; ps->capacity = newcapacity; } ps->a[ps->size] = x; ps->size++; AdjustUp(ps->a,ps->size-1); } void AdjustDown(HPDataType* php,int n ,int parent )//向下调整算法 { int child = parent * 2 + 1; if (php[child] > php[child + 1]) child = parent * 2 + 2; while (child < n) { if (php[child] < php[parent]) { Swap(&(php[child]), &(php[parent])); parent = child; child = parent * 2 + 1; } else { break; } } } void HPPop(HP* ps)//删除堆顶数据 { assert(ps); assert(ps->size > 0); Swap(&(ps->a[0]), &(ps->a[ps->size - 1])); ps->size--; AdjustDown(ps->a,ps->size,0); } bool HPEmpty(HP* ps)//判断堆是否为空 { assert(ps); if (ps->size == 0) { return true; } return false; } HPDataType HPTop(HP* ps)//取出堆顶数据 { assert(ps); assert(ps->size > 0); return ps->a[0]; } HPDataType HPSize(HP* ps)//返回堆有效数据个数 { assert(ps); return ps->size; } void HPSort(HPDataType* php,int n)//堆排序 { //降序 建小堆 //升序 建大堆 //建堆的第一种方法 /*for (int i = 1; i < n; i++) { AdjustUp(php, i); }*/ //建堆的第二种方法: for (int i = (n - 1 - 1) / 2; i < n; i++)//(n-1-1)/2是为了找最后一个数据的父亲 { AdjustDown(php, n, i); } int end = n - 1; while (end > 0) { Swap(&(php[0]), &(php[end])); AdjustDown(php, end, 0); end--; } }
test.c
cpp#include"Heap.h" void test01() { HP hp; HPInit(&hp); HPPush(&hp, 6); HPPush(&hp, 2); HPPush(&hp, 3); HPPush(&hp, 4); HPPush(&hp, 10); HPPush(&hp, 9); HPPush(&hp, 7); HPPop(&hp); printf("%d\n", HPTop(&hp)); printf("%d\n", HPSize(&hp)); // HPDestroy(&hp); } void test02() { int arr[] = { 1,2,6,4,5,8,9,7 }; HPSort(&arr,sizeof(arr)/sizeof(int)); } int main() { //test01(); test02(); return 0; }
值得注意的是test.c是本人为了测试各函数是否正常而写出来的。
三.结言
堆的分享就到这了。觉得对自己有帮助的同学,可不可以给个三连。
好啦,同学们我们下期再见,拜拜喽。