Heap堆
Heap堆排序
向上调整
当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。
向上调整算法的基本思想(以建小堆为例):
1.将目标结点与其父结点比较。
2.若目标结点的值比其父节点的值小,则交换目标节点与其父节点的位置,并将原目标节点的父节点当作新的目标节点继续进行向上调整。若目标节点的值比其父节点的值大,则停止向上调整,此时该树已经是小堆了。
向下调整
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};
堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。
int a[] = {1,5,3,8,7,6};
上图,从节点3
开始调,将3
和6
看作一个树来调整,然后再调节点5
,当根节点的两个儿子节点都满足堆的性质,就可以对根节点进行向上/下调整了。
堆排序
建堆
利用堆删除的思想来进行排序
将首尾进行交换,然后向下调整(不考虑交换到后面的元素,将剩余的元素当作堆来调整)
Heap.h文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HeapInit(HP* php);
void HeapPush(HP* php, HPDataType val);
void HeapPop(HP* php);
void AdjustUp(HPDataType* a,int child);
void AdjustDown(HPDataType* a,int sz ,int parent);
void Swap(HPDataType* p1, HPDataType* p2);
void SetHeap(HPDataType* a,int sz);
bool HeapEmpty(HP* php);
HPDataType HeapTop(HP* php);
int HeapSize(HP* php);
Heap.c文件
#include"Heap.h"
void HeapInit(HP* php)
{
assert(php); // 判断指针是否为空
php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4); // 分配堆数组的内存空间
if (php->a == NULL)
{
perror("malloc fail:"); // 输出错误信息
exit(-1); // 退出程序
}
php->size = 0; // 初始化堆的大小为0
php->capacity = 4; // 初始化堆的容量为4
}
void HeapPush(HP* php, HPDataType val)
{
assert(php); // 判断指针是否为空
if (php->size == php->capacity) // 如果堆已经满了,需要扩容
{
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2); // 重新分配内存空间
if (tmp == NULL)
{
perror("realloc fail:"); // 输出错误信息
exit(-1); // 退出程序
}
php->a = tmp; // 将指针指向新的内存空间
php->capacity *= 2; // 更新堆的容量
}
php->a[php->size++] = val; // 将新元素插入到堆的最后面
AdjustUp(php->a, php->size - 1); // 对插入的元素进行向上调整,保持堆的性质
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1; // 交换两个元素的值
*p1 = *p2;
*p2 = tmp;
}
void HeapPop(HP* php)
{
assert(php); // 判断指针是否为空
assert(!HeapEmpty(php)); // 判断堆是否为空
Swap(&php->a[0], &php->a[php->size - 1]); // 将第一个元素和最后一个元素交换位置
php->size--; // 将堆的大小减1
AdjustDown(php->a, php->size, 0); // 对堆进行向下调整,保持堆的性质
}
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2; // 获取当前节点的父节点的下标
while (a[parent] < a[child]) // 如果当前节点比父节点大,就需要进行交换
{
Swap(&a[child], &a[parent]); // 交换当前节点和父节点的值
child = parent; // 更新当前节点的下标
parent = (child - 1) / 2; // 更新父节点的下标
}
}
void AdjustDown(HPDataType* a, int sz, int parent)
{
assert(a); // 判断指针是否为空
int child = parent * 2 + 1; // 获取当前节点的左儿子的下标
while (child < sz) // 如果当前节点有左儿子,就需要进行调整
{
if (child + 1 < sz && a[child] < a[child + 1]) // 如果当前节点有右儿子,并且右儿子比左儿子大,就需要选取右儿子进行交换
{
child++;
}
if (a[child] > a[parent]) // 如果当前节点比父节点大,就需要进行交换
{
Swap(&a[child], &a[parent]); // 交换当前节点和父节点的值
parent = child; // 更新父节点的下标
child = parent * 2 + 1; // 更新当前节点的下标
}
else
{
break; // 如果当前节点比父节点小,就不需要进行调整了,直接退出循环
}
}
}
void SetHeap(HPDataType* a, int sz)
{
assert(a); // 判断指针是否为空
for (int i = 0; i < sz; i++)
{
AdjustUp(a, i); // 对堆进行向上调整,保持堆的性质
}
}
bool HeapEmpty(HP* php){
assert(php); // 判断指针是否为空
return php->size == 0; // 如果堆的大小为0,就说明堆为空
}
HPDataType HeapTop(HP* php)
{
assert(php); // 判断指针是否为空
return php->a[0]; // 返回堆的第一个元素,也就是最大值
}
int HeapSize(HP* php)
{
assert(php); // 判断指针是否为空
return php->size; // 返回堆的大小
}
test.c文件
#include"Heap.h"
void heap_sort(HPDataType* arr,int sz)
{
int num = sz;
SetHeap(arr, sz);
while (sz)
{
Swap(&arr[0], &arr[sz-1]);
sz--;
AdjustDown(arr, sz-1, 0);
}
for (int i = 0; i < num; i++)
{
printf("%d ", arr[i]);
}
}
int main(void)
{
/*
HP hp;
HeapInit(&hp);
HeapPush(&hp, 4);
HeapPush(&hp, 18);
HeapPush(&hp, 42);
HeapPush(&hp, 12);
HeapPush(&hp, 2);
HeapPush(&hp, 3);
HeapPush(&hp, 100);
HeapPush(&hp, 200);
HeapPush(&hp, 300);
HeapPush(&hp, 87);
HeapPush(&hp, 68);
HeapPush(&hp, 33);
HeapPop(&hp);
while (!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
*/
int arr[] = { 1,2,5,3,4,7,8,3,2,8,100,23,52,123,666 };
int sz= sizeof(arr) / sizeof(arr[0]);
//printf("%d ", sz);
heap_sort(arr,sz);
return 0;
}