堆,实际上就是一棵完全二叉树,本篇正是利用这点实现了堆的基本操作,包括构造堆,插入数据,删除堆顶数据,获取堆顶数据,判空,排序以及查找海量数据中最小的前k个数据等等。
头文件
heap.h
# ifndef __HEAP_H__
# define __HEAP_H__
# include <stdio.h>
# include <assert.h>
# include <stdlib.h>
# include <string.h>
# define MAX_SIZE (20)
typedef int DataType;
typedef struct Heap
{
DataType data[MAX_SIZE];
int size;
}Heap;
void HeapInit(Heap * pH, DataType data[], int size); // 堆的初始化
void AdjustDown(Heap * pH, int root); // 向下调整堆
void AdjustUp(Heap * pH, int child); // 向上调整堆
void MakeHeap(Heap * pH); // 构造堆
void HeapPush(Heap * pH, DataType data); // 插入一个数据,保证堆的性质仍然满足
void HeapPop(Heap * pH); // 删除堆顶数据,保证堆的性质仍然满足
DataType * FindTopK(DataType data[], int size, int k); // 查找海量数据中最小的前K个数据
int HeapTop(Heap * pH); // 获取堆顶数据
void HeapSort(DataType data[], int size); // 堆排序
void DataPrint(DataType data[], int size); // 打印数组元素
int HeapIsEmpty(Heap * pH); // 判断堆是否为空,返回1表示堆为空,返回0表示堆不为空
int HeapSize(Heap * pH); // 堆的大小
void HeapDestory(Heap * pH); // 堆的销毁
# endif // __HEAP_H__
难点剖析
1.堆排序
a.从小到大排序构造大堆,然后两两交换(ps:交换首元素与最后一个元素),每一次交换后向下调整,保持大堆的状态,这样就可以依次得到最大的元素、次大的元素...
b.从大到小排序构造小堆,然后两两交换(ps:交换首元素与最后一个元素),每一次交换后向下调整,保持小堆的状态,这样就可以依次得到最小的元素、次小的元素...
2.删除堆顶元素
用最后一个元素去替换首元素,这样就相当于减少了一个元素,然后再向下调整,保持小(大)堆的状态,这样就实现了删除堆顶元素。
3.查找海量数据中最小的前k个数据
首先构造包含k个数据的大堆,然后从海量数据中的第k+1个数据开始,依次与堆中数据的最大值,也就是与堆顶元素进行比较,如果比堆顶元素小,那么就交换两者的数据,并不断向下调整,直到找到最小的前k个数据。
源代码
1.heap.c
#define _CRT_SECURE_NO_WARNINGS 1
/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名称:heap.c
* 功能:堆的基本操作内部实现细节
*
* 当前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月20日10:35:44
*/
# include "heap.h"
/*
* 函数名称:HeapInit
*
* 函数功能:堆的初始化
*
* 入口参数:pH, data, size
*
* 出口参数:void
*
* 返回类型:void
*/
void HeapInit(Heap * pH, DataType data[], int size)
{
assert(NULL != pH);
assert(size <= MAX_SIZE);
pH->size = size;
memcpy(pH->data, data, sizeof(DataType) * size);
return;
}
/*
* 函数名称:Swap
*
* 函数功能:交换数据
*
* 入口参数:a, b
*
* 出口参数:void
*
* 返回类型:void
*/
void Swap(DataType *a, DataType *b)
{
DataType tmp = *a;
*a = *b;
*b = tmp;
return;
}
/*
* 函数名称:AdjustDown
*
* 函数功能:向下调整堆(递归)
*
* 入口参数:pH, root
*
* 出口参数:void
*
* 返回类型:void
*/
//void AdjustDown(Heap * pH, int root)
//{
// int minChild = 0;
// int left = root * 2 + 1;
// int right = root * 2 + 2;
//
// assert(NULL != pH);
//
// //越界
// if (root >= pH->size)
// {
// return;
// }
// else if (left >= pH->size)
// {
// return;
// }
// else
// {
// ;
// }
// //假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
// minChild = left;
//
// //确保右孩子存在,并且右孩子比左孩子小
// //right最大即为数组最后一个下标,其小于pH->size
// if ((right < pH->size) && (pH->data[right] < pH->data[left]))
// {
// minChild = right;
// }
// else
// {
// ;
// }
//
// if (pH->data[minChild] < pH->data[root])
// {
// Swap(pH->data + minChild, pH->data + root);
// AdjustDown(pH, minChild);
// }
// else
// {
// ;
// }
//
// return;
//}
/*
* 函数名称:AdjustDown
*
* 函数功能:向下调整堆(循环)
*
* 入口参数:pH, root
*
* 出口参数:void
*
* 返回类型:void
*/
void AdjustDown(Heap * pH, int root)
{
int minChild = 0;
int parent = root;
int left = 0;
int right = 0;
assert(NULL != pH);
//越界
while (parent < pH->size)
{
left = parent * 2 + 1;
right = parent * 2 + 2;
if (left >= pH->size)
{
return;
}
else
{
;
}
//假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
minChild = left;
//确保右孩子存在,并且右孩子比左孩子小
//right最大即为数组最后一个下标,其小于pH->size
if ((right < pH->size) && (pH->data[right] < pH->data[left]))
{
minChild = right;
}
else
{
;
}
if (pH->data[minChild] > pH->data[parent])
{
return;
}
else
{
Swap(pH->data + minChild, pH->data + parent);
parent = minChild;
}
}
return;
}
/*
* 函数名称:AdjustUp
*
* 函数功能:向上调整堆(递归)
*
* 入口参数:pH, child
*
* 出口参数:void
*
* 返回类型:void
*/
//void AdjustUp(Heap * pH, int child)
//{
// int parent = (child - 1) / 2;
//
// assert(NULL != pH);
//
// //与根比较
// if(pH->data[child] > pH->data[parent])
// {
// //满足堆
// return;
// }
// else
// {
// Swap(pH->data + child, pH->data + parent);
// }
//
// //已经调整到根结点
// if (0 == parent)
// {
// return;
// }
// else
// {
// AdjustUp(pH, parent);
// }
//
// return;
//}
/*
* 函数名称:AdjustUp
*
* 函数功能:向上调整堆(循环)
*
* 入口参数:pH, child
*
* 出口参数:void
*
* 返回类型:void
*/
void AdjustUp(Heap * pH, int child)
{
int parent = 0;
assert(NULL != pH);
while (1)
{
parent = (child - 1) / 2;
//插入结点在右子树,并且右子树大于左子树
if ((0 == child % 2) && (pH->data[child] > pH->data[child - 1]))
{
//当前结点所在子树已经满足堆
return;
}
else
{
;
}
//与根比较
if(pH->data[child] > pH->data[parent])
{
//满足堆
return;
}
else
{
Swap(pH->data + child, pH->data + parent);
}
//已经调整到根结点
if (0 == parent)
{
return;
}
else
{
child = parent;
}
}
return;
}
/*
* 函数名称:AdjustSortDown
*
* 函数功能:向下调整堆(递归) 大堆
*
* 入口参数:data, size, root
*
* 出口参数:void
*
* 返回类型:void
*/
void AdjustSortDown(DataType data[], int size, int root)
{
int maxChild = 0;
int left = root * 2 + 1;
int right = root * 2 + 2;
//越界
if (root >= size)
{
return;
}
else if (left >= size)
{
return;
}
else
{
;
}
//假设左孩子大,如果左孩子小的话会在if判断语句中进行调整
maxChild = left;
//确保右孩子存在,并且右孩子比左孩子大
//right最大即为数组最后一个下标,其大于size
if ((right < size) && (data[right] > data[left]))
{
maxChild = right;
}
else
{
;
}
if (data[maxChild] > data[root])
{
Swap(data + maxChild, data + root);
AdjustSortDown(data, size, maxChild);
}
else
{
;
}
return;
}
/*
* 函数名称:MakeHeap
*
* 函数功能:构造堆
*
* 入口参数:pH
*
* 出口参数:void
*
* 返回类型:void
*/
void MakeHeap(Heap * pH)
{
int i = 0;
int minChild = 0;
int left = 0;
int right = 0;
assert(NULL != pH);
//最后一个结点是size-1,那么最后一个结点的双亲结点是((size-1)-1)/2
for (i = (pH->size - 2) / 2; i >= 0; i--)
{
left = i * 2 + 1;
right = i * 2 + 2;
//假设左孩子小,如果左孩子大的话会在if判断语句中进行调整
minChild = left;
//确保右孩子存在,并且右孩子比左孩子小
//right最大即为数组最后一个下标,其小于pH->size
if ((right < pH->size) && (pH->data[right] < pH->data[left]))
{
minChild = right;
}
else
{
;
}
if (pH->data[i] > pH->data[minChild])
{
Swap(pH->data + i, pH->data + minChild);
AdjustDown(pH, minChild);
}
else
{
;
}
}
return;
}
/*
* 函数名称:HeapPush
*
* 函数功能:插入一个数据,保证堆的性质仍然满足
*
* 入口参数:pH, data
*
* 出口参数:void
*
* 返回类型:void
*/
void HeapPush(Heap * pH, DataType data)
{
assert(NULL != pH);
assert(pH->size < MAX_SIZE);
//尾插
pH->data[pH->size++] = data;
AdjustUp(pH, pH->size - 1);
return;
}
/*
* 函数名称:HeapPop
*
* 函数功能:删除堆顶数据,保证堆的性质仍然满足
*
* 入口参数:pH
*
* 出口参数:void
*
* 返回类型:void
*/
void HeapPop(Heap * pH)
{
assert(NULL != pH);
assert(pH->size > 0);
//用数组中的最后一个元素去替换首元素,相当于减少一个元素,再向下调整
pH->data[0] = pH->data[pH->size - 1];
AdjustDown(pH, 0);
return;
}
/*
* 函数名称:HeapSort
*
* 函数功能:堆排序
*
* 入口参数:data, size
*
* 出口参数:void
*
* 返回类型:void
*/
void HeapSort(DataType data[], int size)
{
//建大堆
int i = 0;
int left = 0;
int right = 0;
int maxChild = 0;
for (i = (size - 2) / 2; i >= 0; i--)
{
left = i * 2 + 1;
right = i * 2 + 2;
//假设左孩子大
maxChild = left;
//右孩子存在,并且右孩子大于左孩子
if ((right < size) && (data[right] > data[left]))
{
maxChild = right;
}
else
{
;
}
if (data[maxChild] > data[i])
{
Swap(data + maxChild, data + i);
AdjustSortDown(data, size, maxChild);
}
else
{
;
}
}
//建好堆后
for (i = 0; i < size; i++)
{
Swap(data, data + size - 1 - i);
AdjustSortDown(data, size - 1 - i, 0);
}
return;
}
/*
* 函数名称:DataPrint
*
* 函数功能:打印数组中的元素
*
* 入口参数:data, size
*
* 出口参数:void
*
* 返回类型:void
*/
void DataPrint(DataType data[], int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d ", data[i]);
}
printf("\n");
return;
}
/*
* 函数名称:HeapTop
*
* 函数功能:获取堆顶数据
*
* 入口参数:pH
*
* 出口参数:pH->data[0]
*
* 返回类型:int
*/
int HeapTop(Heap * pH)
{
assert(NULL != pH);
assert(pH->size > 0);
return pH->data[0];
}
/*
* 函数名称:FindTopK
*
* 函数功能:查找海量数据中最小的前K个数据
*
* 入口参数:data, size, k
*
* 出口参数:newdata
*
* 返回类型:DataType *
*/
DataType * FindTopK(DataType data[], int size, int k)
{
int i = 0;
int left = 0;
int right = 0;
int maxChild = 0;
DataType * newdata = (DataType *)malloc(sizeof(DataType) * k);
assert(NULL != newdata);
memcpy(newdata, data, sizeof(DataType) * k);
//在newdata上构造大堆,并且堆中有k个数据
for (i = (k - 2) / 2; i >= 0; i--)
{
left = i * 2 + 1;
right = i * 2 + 2;
//假设左孩子大
maxChild = left;
//右孩子存在,并且右孩子大于左孩子
if ((right < k) && (newdata[right] > newdata[left]))
{
maxChild = right;
}
else
{
;
}
if (newdata[maxChild] > newdata[i])
{
Swap(newdata + maxChild, newdata + i);
AdjustSortDown(newdata, k, maxChild);
}
else
{
;
}
}
//堆构造成功
for (i = k; i < size; i++)
{
//由于构造的是大堆,因此查找的原始数组中的数据要比堆中最大的数据小
if (data[i] < newdata[0])
{
newdata[0] = data[i];
AdjustSortDown(newdata, k, 0);
}
else
{
;
}
}
return newdata;
}
/*
* 函数名称:HeapIsEmpty
*
* 函数功能:判断堆是否为空,返回1表示堆为空,返回0表示堆不为空
*
* 入口参数:pH
*
* 出口参数:1 or 0
*
* 返回类型:int
*/
int HeapIsEmpty(Heap * pH)
{
assert(NULL != pH);
return 0 == pH->size ? 1 : 0;
}
/*
* 函数名称:HeapSize
*
* 函数功能:堆的大小
*
* 入口参数:pH
*
* 出口参数:pH->size
*
* 返回类型:int
*/
int HeapSize(Heap * pH)
{
assert(NULL != pH);
return pH->size;
}
/*
* 函数名称:HeapDestory
*
* 函数功能:堆的销毁
*
* 入口参数:pH
*
* 出口参数:void
*
* 返回类型:void
*/
void HeapDestory(Heap * pH)
{
assert(NULL != pH);
pH->size = 0;
return;
}
2.test.c
#define _CRT_SECURE_NO_WARNINGS 1
/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名称:test.c
* 功能:测试堆的基本操作
*
* 当前版本:V1.0
* 作者:sustzc
* 完成日期:2018年6月20日10:36:37
*/
# include "heap.h"
/*
* 函数名称:main
*
* 函数功能:测试主程序
*
* 入口参数:void
*
* 出口参数:0
*
* 返回类型:int
*/
int main(void)
{
Heap heap;
int i = 0;
int * newdata = NULL;
DataType data[] = {12, 10, 8, 9, 2, 11, 1};
HeapInit(&heap, data, sizeof(data) / sizeof(DataType));
MakeHeap(&heap);
printf("插入数据之前,堆顶元素为: %d\n", HeapTop(&heap));
HeapPush(&heap, 0);
printf("插入数据之后,堆顶元素为: %d\n", HeapTop(&heap));
HeapPop(&heap);
printf("删除堆顶数据之后,堆顶元素为: %d\n", HeapTop(&heap));
newdata = FindTopK(data, sizeof(data) / sizeof(DataType), 3);
printf("数组中最小的前3个数分别是:\n");
for (i = 0; i < 3; i++)
{
printf("%d\t", newdata[i]);
}
printf("\n排序之前:\n");
DataPrint(data, sizeof(data) / sizeof(DataType));
HeapSort(data, sizeof(data) / sizeof(DataType));
printf("排序之后:\n");
DataPrint(data, sizeof(data) / sizeof(DataType));
HeapDestory(&heap);
return 0;
}
输出结果