1.什么是堆?
堆可以看作是一颗特殊的完全二叉树,每个节点的左右孩子的值都大于或小于该节点的值。
根据大小关系可以将堆分为大根堆(最大堆)和小根堆(最小堆)两大类。
大根堆:
小根堆:
2.堆的实现
堆的实现采用数组,堆的操作基于向下调整算法和向上调整算法
1)堆的创建
方法1:从倒数第1个非叶子节点直到根节点,开始执行向下调整算法
方法2:执行堆的插入操作
2)堆的插入
时间复杂度:O(logN)
将要插入的数据添加到数据的尾部,开始执行向上调整算法。
3)堆的删除
时间复杂度:O(logN)
堆只能删除堆顶元素
将堆顶元素和尾部元素进行交换,删除尾部元素,从根节点开始执行向下调整算法
4)堆排序
时间复杂度:O(NlogN)
一个大根堆进行堆排序可以得到一个递增的序列,
一个小根堆进行堆排序可以得到一个递减的序列
堆排序的过程:
将堆顶元素和尾部元素进行交换,从根节点开始执行向下调整算法,每次让尾部元素往前移动一位,直到只剩下一个堆顶元素时就不再调整
5)TopK:打印最小的或者最大的k个元素
拿到堆顶元素,进行堆的删除操作,重复这一步k次
3.源码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define ORIGINSIZE 10 //最初的堆空间存储元素的个数
//大堆
typedef int DataType; //数据元素类型
typedef struct
{
DataType* data; //数据域
int curSz; //元素个数
int capacity; //容量
}Heap;
//交换堆两个下标处的数据
void Swap(Heap* hp, int pos1, int pos2)
{
int tmp = hp->data[pos1];
hp->data[pos1] = hp->data[pos2];
hp->data[pos2] = tmp;
}
//堆的向下调整---------建堆时会用到这个接口
//changeSize表示要调整的元素个数
//时间复杂度:O(logN)
void ShiftDown(Heap* hp, int changeSize, int parent)
{
if (hp == NULL || hp->curSz == 0)
return;
int child = parent * 2 + 1; //左孩子的下标
while (child < changeSize)
{
if (child + 1 < changeSize && hp->data[child + 1] > hp->data[child])
++child;
//不符合堆条件就交换数据
if (hp->data[parent] < hp->data[child])
Swap(hp, parent, child);
//符合堆条件就停止调整
else
break;
//继续向下调整
parent = child;
child = 2 * parent + 1;
}
}
//堆的向上调整---堆中插入数据会用到
//changeSize表示要调整的元素个数
//时间复杂度:O(logN)
void ShiftUp(Heap* hp, int changeSize, int child)
{
if (hp == NULL || hp->curSz == 0)
return;
int parent = (child - 1) / 2; //父节点的下标
//当child=0时说明了此时已经调整到根节点了
while (child > 0)
{
if (hp->data[parent] < hp->data[child])
Swap(hp, parent, child);
else
break;
child = parent;
parent = (child - 1) / 2;
}
}
//判空
bool IsEmpty(Heap* hp)
{
if (hp == NULL || hp->curSz == 0)
return true;
return false;
}
//判断数据是否已满
bool IsFull(Heap* hp)
{
if (hp == NULL)
return false;
return hp->capacity == hp->curSz;
}
//检查容量
void CheckCapacity(Heap* hp)
{
if (hp == NULL)
return;
if (IsFull(hp))
{
int newC = 2 * hp->capacity;
hp->data = (DataType*)realloc(hp->data, sizeof(DataType)* newC);
hp->capacity = newC;
}
}
//插入数据
//时间复杂度:O(logN)
bool HeapPush(Heap* hp, DataType val)
{
if (hp == NULL)
return false;
//检查容量,如果当前的堆空间已满,就开始增容
CheckCapacity(hp);
//插入数据并进行向上调整
hp->data[hp->curSz] = val;
ShiftUp(hp, hp->curSz + 1, hp->curSz);
++hp->curSz;
return true;
}
//删除数据
bool HeapPop(Heap* hp)
{
if (IsEmpty(hp))
return false;
//交换堆的根节点和尾节点的数据
Swap(hp, 0, hp->curSz - 1);
//更新元素个数
--hp->curSz;
//从根节点开始执行向下调整
ShiftDown(hp, hp->curSz, 0);
return true;
}
//建堆
Heap* CreateHeap(DataType* data, int eleSize)
{
//1.开辟空间
Heap* hp = (Heap*)malloc(sizeof(Heap));
hp->curSz = 0;
hp->capacity = ORIGINSIZE; //刚开始开辟能存储ORIGINSIZE个元素的堆空间
hp->data = (DataType*)calloc(hp->capacity, sizeof(DataType));
//2.插入数据
//循环了N次(N为数据规模)
for (int i = 0; i < eleSize; ++i)
{
//插入时间复杂度为O(logN)
HeapPush(hp, data[i]);
}
return hp;
}
//销毁堆
void DestroyHeap(Heap* hp)
{
if (hp == NULL)
return;
free(hp->data);
free(hp);
}
//获取元素个数
int GetHeapSize(Heap* hp)
{
if (hp == NULL)
return 0;
return hp->curSz;
}
//获取堆顶元素
DataType GetHeapTop(Heap* hp)
{
return hp->data[0];
}
//堆排序
void HeapSort(Heap* hp)
{
if (IsEmpty(hp))
return;
//将头尾节点交换并执行向下调整
int end = hp->curSz - 1;
//外循环了N次
while (end > 0)
{
Swap(hp, 0, end);
//向下调整的时间复杂度为O(logN)
ShiftDown(hp, end, 0);
--end;
}
}
//TopK:打印最大的K个数
void PrintTopK(Heap* hp, int k)
{
if (IsEmpty(hp))
return;
if (k > hp->curSz)
return;
//拿到堆顶元素之后并进行删除,执行k次
while (k--)
{
printf("%d ", GetHeapTop(hp));
HeapPop(hp);
}
printf("\n");
}
//清空堆
void ClearHeap(Heap* hp)
{
if (IsEmpty(hp))
return;
hp->curSz = 0;
}
//堆遍历
void HeapTraverse(Heap* hp)
{
if (IsEmpty(hp))
return;
for (int i = 0; i < hp->curSz; ++i)
{
printf("%d ", hp->data[i]);
}
printf("\n");
}