目录
前言:
一、顺序存储结构:
二、堆:
1.堆的性质:
2.堆的性质:
3.堆的实现:
Ⅰ.堆的初始化:
Ⅱ.堆的插入(含向上调整):
Ⅲ.堆的删除(含向下调整算法):
Ⅳ.取堆顶的数据:
Ⅴ.堆中的数据个数:
Ⅵ.堆的判空:
Ⅶ.堆的销毁:
总结:
前言:
上篇文章中,我们认识了树与二叉树相关概念以及两种常见的存储结构,而今天我们将对顺序存储结构进行实现的讲解。
一、顺序存储结构:
普通的二叉树是是不适合用数组来存储的,因为可能会存在大量的空间浪费,但是完全二叉树不会存在此问题,所以更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来进行存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆完全没有关系。今天我们关于顺序存储结构的讲解,就以堆的形式进行。
二、堆:
1.堆的性质:
堆分为小根堆和大根堆,根节点始终小于子节点称为小根堆,相反根节点大于子节点则称为大根堆。换句话说,根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.堆的性质:
①.堆中的某个节点的值总是小于或不小于其父节点的值。
②.堆总是一颗完全二叉树
3.堆的实现:
Heap.h:存放函数声明、包含其他头文件、定义宏。
Heap.c:书写函数定义,书写函数实现。
test.c:书写程序整体执行逻辑。
Ⅰ.堆的初始化:
首先判断传入指针是否为空,然后为其开辟空间,并对相应数据进行赋值。
void HeapInit(HP* p) { assert(p); HPDataType* new = (HPDataType*)malloc(sizeof(HPDataType)*4); if (new == NULL) { perror("malloc fail"); return; } p->a = new; p->size = 0; p->capacity = 4; }
Ⅱ.堆的插入(含向上调整):
因为堆的存储在物理层面上是数组,但是在逻辑层面上是二叉树。并且由于只有大根堆和小根堆,所以在插入数据之后要保准仍然是堆,就需要进行适当的调整。插入时从尾部插入,,而是否为堆取决于子节点和父节点的关系,所以插入的数据要与其父节点进行比较,若为小根堆则子节点要比父节点要大,否则就需要交换子节点和父节点,大根堆则相反。这种调整方式就叫做向上调整。
执行操作前进行非空判断,防止对空指针进行操作;
插入前判断空间是否足够,若不足则进行扩容;
插入后为了保证数组仍然为堆,所以需要在插入后进行向上调整。
//向上调整 void Adujustup(HPDataType* a, int child) { assert(a); int parent = (child - 1) / 2; while (child > 0) { if (a[child] > a[parent]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } //堆插入 void HeapPush(HP* p, HPDataType x) { assert(p); if (p->size == p->capacity) { HPDataType* new = (HPDataType*)realloc(p->a,sizeof(HPDataType) * p->capacity*2); if (new == NULL) { perror("realloc fail"); return; } } p->a[p->size] = x; p->size++; Adujustup(p->a, p->size - 1); }
Ⅲ.堆的删除(含向下调整算法):
堆删除的实质时删除堆顶元素,但是如果我们直接删除堆顶元素,就会破坏堆的结构,所以这种方法不可取;于是我们这里采用将堆顶的数据于最后一个数据交换,再删除最后一个数据的方法,这样就实现了堆顶数据的删除。接着我们再调整以下堆顶数据的位置就可以了。
而在这里我们就需要向下调整,所以我们选择的调整方法就是:将根节点与它的孩子中的较小的值交换,然后再将交换后的节点作为父节点继续与他的子节点交换,直到该节点小于他的子节点,或者成为叶节点。要注意的是,使用这个方法有一个前提:根节点的两个子树也得是堆才行。
执行操作前进行非空判断,防止对空指针进行操作;
删除过程同样与队列近乎一致,不同点是在删除过后为了保证删除堆顶数据后仍为堆,于是需要使用向下调整对删除后的对进行调整。文章来源:https://www.toymoban.com/news/detail-564310.html
//向下调整 void Adujustdown(HPDataType* a, int size, int parent) { int child = parent * 2 + 1; while (child < size-1) { if (a[child] < a[child + 1]) { child++; } if (a[parent] < a[child]) { Swap(&a[parent], &a[child]); parent = child; child = parent * 2 + 1; } else { break; } } } //堆删除 void HeapPop(HP* p) { assert(p); if (p->size == 0) { return; } Swap(&p->a[0], &p->a[p->size - 1]); p->size--; Adujustdown(p->a, p->size, 0); }
Ⅳ.取堆顶的数据:
HPDataType HeapTop(HP* p) { assert(p); return p->a[0]; }
Ⅴ.堆中的数据个数:
int HeapSize(HP* p) { assert(p); return p->size; }
Ⅵ.堆的判空:
bool HeapEmpty(HP* p) { assert(p); return p->size == 0; }
Ⅶ.堆的销毁:
void HeapDestroy(HP* p) { assert(p); free(p->a); p->a = NULL; p->capacity = 0; p->size = 0; }
总结:
这篇文章我们完整的认识、了解、学习了二叉树顺序存储结构的相关知识,并且对二叉树顺序存储进行实现——堆的各接口实现。至此,关于二叉树的顺序存储的知识我们就全部学习完毕了。 文章来源地址https://www.toymoban.com/news/detail-564310.html
到了这里,关于【数据结构】树与二叉树(中)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!