目录
前言
上一章中我们学会了二叉树里的顺序存储实现通常新建一个结构体去堆生成,堆中存在大堆和小堆的结构,本章我们的内容主要是如何实现不新建结构体的情况下,通过在原来的数组进行大小堆中的调整中进行排序。
一、堆调整
1.向上调整方法
在上一章中我们学会如何在进行插入数据的情况下保证堆的结构规则不变,在插入数据时进行向上调整即可,那么我们在原数组也很简单,只需要控制数组的有效范围进行模拟堆插入数据的状态就行啦,我们还是画图理解吧:
我们先假设用这数组作为案例进行小堆调整:int arr[] = { 46, 7, 2, 4, 3, 7, 9 };
从对堆的理解,以上数组既不是大堆也不是小堆,我们开始调整了,首先假设 46 是第一个插入的数据,第一个数据没有任何数据需要对比所以,不需要任何调整。
那么我们就继续上一步操作,假设插入第二个数据 7 ,进行向上调整判断:
进行调整完后,继续第二步的操作,假设进行插入 2 这个数,也进行向上调整:
可以发现以上前三位元素数据就进行了向上调整了,符合堆的定义,那么后面的数据也是一样的,假设插入数据进行向上调整,具体代码的话可以参考一下:
//向上调整
void AjionUp(HPDatatpye* arr, int child) {
int parent = (child - 1) / 2;
while (child) {
if (arr[parent] < arr[child]) {
Swap(&arr[parent], &arr[child]);
child = parent;
parent = (child - 1) / 2;
}
else {
break;
}
}
}
//数组中向上调整
for (int i = 1; i < n; i++)
AjionUp(arr, i);
2.向下调整法
上面的向上调整法我们大致讲清楚了,那么向下调整法该如何理解呢?他也可以实现吗?那么向下调整法该如何去理解和操作呢?
向下调整法会和向上调整法会稍微有些不一样,他是通过从父节点从后往前去逐个去调整的,那么步骤呢我也会通过画图的方法给大家展示出来。
我们先假设用这数组作为案例进行小堆调整:int arr[] = { 42, 21, 9, 8 ,1 ,7 ,2 };
首先初始的情况是这样的,可以看出他是一个大堆,我们把它转换成小堆:
首先想想上一章中我们是如何进行向下调整的,我们是从最后一个节点找到父节点进行调整的,那么在这边也是一样,在向上调整我们是假设一个一个数据进行插入从而调整的,那么向下调整就不一样了是从最后一个父节点逐个往前那进行向下调整:
我们从上面求出的父节点位置往前移动一位,继续进行向下调整:
继续以上的操作:
最后我们调整完成,小堆为:1,8,2,42,21,7,9
二、堆排序
我们对数组进行了堆调整之后,我们其实可以通过上一章发现一个规律的,无论是大堆还是小堆,堆顶上的数据要么是最大值或最小值的,那我们进行头删的时候,删除的元素必定是最大值或最小值,我们还是以小堆为准,我们要思考的是假设如果用小堆进行全数据头删除并且向下调整之后,数组中的数据是升序还是降序?这时候就是需要思考的时候了。
当然也是要按需求而定,当需要升序的时候就要用大堆了,降序就需要用小堆了,为什么呢?还是一样上图讲解:
我们还是一样用这个直接现成好的大堆进行堆排序,当我们需要降序的排序就需要用到大堆进行排序,经过刚刚说的思路,大堆进行删除数据,我们堆顶就会和堆尾进行数据交换,我们上一章也讲到要对堆进行头删就要进行向下调整,那么我们这边也一样。
好,那么我们开始进行排序,用上刚刚上面说的思路,先假设头删,头尾交换,然后进行向下调整:
经过第一次的排序,我们已经把最大数42放到最后面了,经过向下调整也把次大数调整到了堆顶,那么我们继续进行上一个操作:
经过第二次的调整,我们就成功把次大值放到了倒数第二位,那么继续进行:
第三个大的数也成功调整了,剩下的数也照常进行操作即可,最终的排序顺序是以下:
堆排序是一个非常快的排序,后续我会给大家写一篇关于排序的博客,那时候我们再探讨排序的时间复杂度,排序也是一个非常有意思的内容,大家可以先期待一下hhh。
三、完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
typedef int HPDatatpye;
void Swap(HPDatatpye* p, HPDatatpye* q) {
HPDatatpye emp = *p;
*p = *q;
*q = emp;
}
void AjionUp(HPDatatpye* arr, int child) {
int parent = (child - 1) / 2;
while (child) {
if (arr[parent] < arr[child]) {
Swap(&arr[parent], &arr[child]);
child = parent;
parent = (child - 1) / 2;
}
else {
break;
}
}
}
void AjoinDown(HPDatatpye* arr, int size, int parent) {
int child = parent * 2 + 1;
while (child < size) {
if (child + 1 < size && arr[child] < arr[child + 1]) {
child = child + 1;
}
if (arr[parent] < arr[child]) {
Swap(&arr[parent], &arr[child]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
void HeapSort(HPDatatpye *arr ,int n) {
//大堆向上调整
/*for (int i = 1; i < n; i++)
AjionUp(arr, i);*/
//大堆向下取整
int parent = (n - 1 - 1) / 2;
while (parent >= 0) {
AjoinDown(arr, n, parent);
parent--;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
int arr_size = n - 1;
while (arr_size) {
Swap(&arr[0], &arr[arr_size]);
AjoinDown(arr, arr_size, 0);
arr_size--;
}
}
int main() {
HPDatatpye arr[] = { 42, 21, 9, 8 ,1 ,7 ,2 };
HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
printf("%d ", arr[i]);
}
printf("\n");
}
总结
本章的知识点是上章的拓展知识点,在二叉树的顺序结构都是需要掌握的,因为后续我们的堆排也是跟本章的知识点是一样的,下一章我会给大家讲解一下二叉树的链式存储结构该如何创建以及生成树的,谢谢大家看到这里。