前言
堆(heap):是堆内存的简称,堆是动态分配内存,内存大小不固定,也不会自动释放,堆——数据结构是一种无序的树状结构,同时它还满足key-value键值对的存储方式。
1. 堆的概念及结构
如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
2.堆的实现
2.1堆的向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int array[] = {27,15,19,18,28,34,65,49,25,37};
代码实现:
//向下调整算法
void AdjustDown(int* a, int n, int parent)
{
int child = parent*2 + 1;
while (child < n)
{
if (child + 1 < n && a[child] > a[child + 1])
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent*2 + 1;
}
else
{
break;
}
}
}
2.2堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
int a[] = {1,5,3,8,7,6};
代码实现:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
初始化:
//堆初始化
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->capacity = 0;
php->size = 0;
}
2.3建堆时间复杂度
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
因此,建堆的时间复杂度为:O(N).
2.4堆的插入
先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
代码实现:
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) >> 1;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
//插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newCapacity = (php->capacity == 0) ? 4 : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
return;
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
2.5堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
代码实现:
//向下调整算法
void AdjustDown(int* a, int n, int parent)
{
int child = parent*2 + 1;
while (child < n)
{
if (child + 1 < n && 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 HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
3.堆的完整代码实现
Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include <time.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(int* a, int n, int parent);
void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
//
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
Heap.c
#include "Heap.h"
//交换两个元素
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) >> 1;
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
//向下调整算法
void AdjustDown(int* a, int n, int parent)
{
int child = parent*2 + 1;
while (child < n)
{
if (child + 1 < n && 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 HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->capacity = 0;
php->size = 0;
}
//销毁
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php -> size = php->capacity = 0;
free(php);
}
//插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newCapacity = (php->capacity == 0) ? 4 : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
return;
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
// 删除堆顶数据
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
//获取堆顶元素
HPDataType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
//判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//获取堆的大小
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
Test.c
#include "Heap.h"
void Test()
{
HP hp;
HeapInit(&hp);
int a[] = { 65,100,70,32,50,60 };
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
HeapPush(&hp, a[i]);
}
while (!HeapEmpty(&hp))
{
int top = HeapTop(&hp);
printf("%d\n", top);
HeapPop(&hp);
}
}
void HeapSort(int* a, int n)
{
//升序,建大堆
//降序,建小堆
//向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
//在调整,选出次小的数
AdjustDown(a, end, 0);
end--;
}
}
void CreatData()
{
//造数据
int n = 1000;
srand((unsigned int)time(NULL));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
printf("fopen fail");
return;
}
for (size_t i = 0; i < n; i++)
{
int x = rand() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
PrintTop(int k)
{
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
printf("fopen fail");
return;
}
int* hipple = (int*)malloc(sizeof(int) * k);
if (hipple == NULL)
{
printf("malloc fail");
return;
}
for (size_t i = 0; i < k; i++)
{
fscanf(fout, "%d",&hipple[i]);
}
//建小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(hipple, k, i);
}
int val = 0;
while (!feof(fout))
{
fscanf(fout, "%d", &val);
if (hipple[0] < val)
{
hipple[0] = val;
AdjustDown(hipple, k, 0);
}
}
for (size_t i = 0; i < k; i++)
{
printf("%d ", hipple[i]);
}
printf("\n");
}
int main()
{
//Test();
/*int a[] = { 1,4,5,7,8,3,5,9,2 };
HeapSort(a, sizeof(a) / sizeof(int));
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("%d ", a[i]);
}*/
//CreatData();
PrintTop(5);
return 0;
}
总结
通过本次堆的实现,加深了对堆这个结构的认识,同时了解了堆的实际应用——1.堆排序和2.TOP-K问题