Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<math.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
//堆的初始化
void HeapInit(HP* php);
//堆的销毁
void HeapDestroy(HP* php);
//向上调整(数组地址,向上调整的子节点坐标)
void AdjustUp(HPDataType* a, int child);
//插入新数据,并且保证还是堆。
void HeapPush(HP* php, HPDataType x);
//删除堆顶数据,并且保证还是堆。
void HeapPop(HP* php);
//取堆顶的数据
HPDataType HeapTop(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);
//返回堆的节点个数
int HeapSize(HP* php);
//打印堆
void HeapPrint(HP* php);
//向下调整的递归写法
//void DownDiGui(HPDataType* arr, int size, int father);
Heap.c
#include"Heap.h"
//堆的初始化
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
//堆的销毁
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
//(子节点)向上调整成小根堆
void AdjustUp(HPDataType* arr,int child)
{
int father = (child - 1) / 2;
while (child > 0)//如果一直向上调整,那么当arr[child]作为根节点时,结束循环。
{
if (arr[child] < arr[father])//向上调整一次
{
HPDataType tmp = arr[child];
arr[child] = arr[father];
arr[father] = tmp;
child = father;
father = (child - 1) / 2;//当arr[child]为根节点时,father的值依然是0 因为[(0-1)/2==0]。
}
else//当前节点的值大于等于其父节点时,无需再继续调整。
{
break;
}
}
}
//(父节点)向下调整成小根堆
void AdjustDown(HPDataType* arr,int size,int father)
{
assert(arr);
int son = father * 2 + 1;//这个son是左子节点的下标
while (son < size)//有左子树才能继续向下调整
{
//存在右子节点,并且右子节点小于左子节点,son就变成右子节点。son最终表示的都是最小的节点.
//son+1小于size表示存在右节点,并且这个表达式要写在&&的左边,防止不存在右节点时arr[son+1]越界访问.
if (son + 1 < size && arr[son + 1] < arr[son])
{
son++;
}
if (arr[son] < arr[father])//如果最小的子节点小于父节点,就互换两个节点。
{
HPDataType tmp = arr[son];
arr[son] = arr[father];
arr[father] = tmp;
father = son;
son = father * 2 + 1;
}
else//最小的子节点都大于父节点,不需要再继续换了,跳出循环。
{
break;
}
}
}
//插入新数据,并且保证还是堆。
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)//空间不足则扩容
{
//首次扩容给4个空间,否则扩容为原来的两倍.
int new_capacity = ((php->capacity) == 0) ? 4 : (2 * php->capacity);
//用临时指针tmp接收realloc得到的空间地址
HPDataType* tmp = (HPDataType*)realloc(php->a,new_capacity * sizeof(HPDataType));
//tmp不为空则php->a接收tmp.否则扩容失败.
if (tmp)
{
php->a = tmp;
php->capacity = new_capacity;
}
else
{
printf("realloc扩容失败\n");
perror("realloc:");
return;
}
}
php->a[php->size] = x;
php->size++;
//向上调整(小根堆)
AdjustUp(php->a,php->size-1);
}
//删除堆顶数据,并且保证还是堆。
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);//至少得有一个数据
//将最后一个节点和根节点互换后。
HPDataType tmp = php->a[0];
php->a[0] = php->a[php->size - 1];
php->a[php->size - 1] = tmp;
//删除最后一个节点(原来的根节点)
php->size--;
//根节点向下调整
AdjustDown(php->a, php->size, 0);
//DownDiGui(php->a, php->size, 0);
}
//取堆顶的数据
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);//保证堆中有数据
return php->a[0];
}
//判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;//判断堆中元素是否为0
}
//返回堆的节点个数
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
//打印堆
void HeapPrint(HP* php)
{
assert(php);
int cen = 1;
for (int i = 0; i < php->size; i++)
{
printf("%2d ", php->a[i]);
if (i == pow(2, cen) - 2)
{
printf("\n");
cen++;
}
}
printf("\n\n");
}
//向下调整的递归写法(小根堆)
//void DownDiGui(HPDataType* arr, int size, int father)
//{
// int son = 2 * father + 1;//算出左节点
// if (son < size)//存在左子树才能进行判断是否与父节点互换.
// {
// if (son + 1 < size && arr[son + 1] < arr[son])//存在右节点就选出最小的节点出来
// {
// son++;
// }
// if (arr[father] > arr[son])//互换节点
// {
// HPDataType tmp = arr[father];
// arr[father] = arr[son];
// arr[son] = tmp;
// }
// DownDiGui(arr, size, son);
// }
// else
// {
// return;
// }
//
//}
Test.c
#include"Heap.h"
void HeapTest()//测试写出来的小根堆是否正确
{
HP hp;
HeapInit(&hp);
int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
//测试新增元素到堆中
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
HeapPush(&hp, arr[i]);
}
HeapPrint(&hp);
//测试删除堆顶
HeapPop(&hp);
HeapPrint(&hp);
}
void HeapSortPrintTest()//使用小根堆来升序打印数组元素
{
HP hp;
HeapInit(&hp);
int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
//新增元素到小根堆中
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
HeapPush(&hp, arr[i]);
}
HeapPrint(&hp);
//升序打印
while (HeapSize(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
}
/*
* 1、这种排序方法还得先写出一个数据结构。
* 2、并且空间复杂度为O(n^2)效率很低
*/
void HeapSort1()//使用小根堆给数组元素进行排升序
{
HP hp;
HeapInit(&hp);
int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
//新增元素到小根堆中,这里的时间复杂度为O(N*logN)
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
HeapPush(&hp, arr[i]);//该函数使用的是向上调整O(logN)
}
HeapPrint(&hp);
//给数组中的元素排序,这里的时间复杂度为O(N^2)
int i = 0;
while (HeapSize(&hp))
{
arr[i++] = HeapTop(&hp);//取堆顶元素赋值给对应位置的数组元素
HeapPop(&hp);//堆顶元素与最后的元素互换后,删除成为最后的元素的堆顶元素,同时对剩下的元素重新建堆(从最后一个分支节点开始向下调整)。
}
//按顺序打印数组元素
/*for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++)
{
printf("%d ", arr[j]);
}*/
}
void HeapSort2(int* arr,int size)
{
//向上调整建堆方式:O(N*logN)
//就当arr[i]是已经插入的新元素,插入新元素就向上调整,这里的函数是调整为小根堆。
for (int i = 1; i < size; i++)
{
AdjustUp(arr, i);//最多可能互换logN次
}
//向下调整建堆方式:O(N)
//向下调整的前提是左子树和右子树都是堆,调整完后整个才是堆,所以必须从最后的第一个分支节点(树)开始调整,叶子节点没有子树无需调整。
for (int i = (size - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, size, i);//对完全二叉树的每一层进行分析,越是下层,调整的节点个数越多,但是调整的次数就越少,越是上层则反之。
}
//建好堆以后形象地打印出来
int cen = 1;
for (int i = 0; i < size; i++)
{
printf("%2d ", arr[i]);
if (i == pow(2, cen) - 2)
{
printf("\n");
cen++;
}
}
printf("\n\n");
//对传递过来的数组元素,选择时间复杂度更小的向下调整的方式建堆。
//升序建大堆!
//降序建小堆!(下面的代码是排降序)
//O(N*logN)
for (int end = size - 1; end > 0; end--)
{
int tmp = arr[0];
arr[0] = arr[end];
arr[end] = tmp;
//向下调整时会和最小的子节点互换,已经排好序的倒数几个节点是不参与互换的,所以互换时左子节点下标必须小于排好序的第一个节点下标。
AdjustDown(arr, end, 0);
//交换 调整一次就形象打印出来便于调试观察
cen = 1;
for (int i = 0; i < size; i++)
{
printf("%2d ", arr[i]);
if (i == pow(2, cen) - 2)
{
printf("\n");
cen++;
}
}
printf("\n\n");
}
}
int main()
{
int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
HeapSort2(arr,sizeof(arr)/sizeof(int));
//按顺序打印数组元素
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
printf("%d ", arr[i]);
}
return 0;
}