目录
一般堆使用顺序结构的数组来存储数据,堆是一种特殊的二叉树。具有二叉树的特性的同时,还具有其它的特性。
一、堆的概念与结构
如果有一个集合,把它所有的元素按完全二叉树的顺序存储方式存储,在一维数组中,并且每一个父节点小于它的孩子结点,则称这个完全二叉树为小堆,而称每一个父节点大于它的孩子结点的完全二叉树为大堆。
- 小堆
- 大堆
性质
根据以上可得,堆总是一棵完全二叉树,且堆中某个结点的值总是不大于或不小于父结点的值。
二叉树的性质
对于一个完全二叉树,按照从上至下从左至右的数组顺序对所有结点从0开始编号,那么对于序号为 i 的结点(i - 1)/ 2 对应的结点是它的父结点,2*i + 1 对应的结点是它的左孩子结点,2*i + 2 对应的结点是它的父结点
二、堆的实现
1、结构
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}Heap;
2、初始化与销毁
//堆的初始化
void HPInit(Heap* php)
{
assert(php);
php->arr = NULL;
php->capacity = php->size = NULL;
}
//堆的销毁
void HPDestroy(Heap* php)
{
assert(php);
if (php->arr)
{
free(php->arr);
}
php->arr = NULL;
php->capacity = php->size = NULL;
}
3、入堆与出堆(小堆)
1)Swap
通过Swap函数交换两个数据
//两个数据的交换
void Swap(HPDataType* x, HPDataType* y)
{
assert(x || y);
HPDataType cpy = *x;
*x = *y;
*y = cpy;
}
2)入堆
1 数据的向上调整
//数据的向上调整(小堆)
void AdjustUpSmall(HPDataType* arr, int child)
{
assert(arr);
int parent = (child - 1) / 2;//求该子节点的父结点
while (child > 0)
{
if (arr[child] < arr[parent])//当孩子结点小于父节点时,两个数据交换
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2 入堆
//入堆(小堆)
void HPPushSmall(Heap* php, HPDataType x)
{
assert(php);
//判断空间是否足够
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr,sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fali!!!");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;//因为数据还没有向上调整,所以size不能++
//向上调整
AdjustUpSmall(php->arr, php->size++);
}
3)出堆
1 数据的向下调整
//数据的向下调整(小堆)
void AdjustDownSmall(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;//求该父节点的左孩子
while (child < n)//该左孩子在数组内
{
if (child + 1 < n && arr[child] > arr[child + 1])//比较左右孩子大小
{
child++;
}
//父节点和孩子结点交换
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
2 出堆
//出堆(小堆)
void HPPopSmall(Heap* php)
{
assert(php && php->size);//有效数据不能为零
//头尾数据交换
Swap(&php->arr[0], &php->arr[php->size - 1]);
//出堆
php->size--;
//数据的向下调整
AdjustDownSmall(php->arr, 0, php->size);
}
三、其他
1、入堆与出堆(大堆)
与小堆大同小异
//数据的向上调整(大堆)
void AdjustUpBig(HPDataType* arr, int child)
{
assert(arr);
int parent = (child - 1) / 2;//求该子节点的父结点
while (child > 0)
{
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//入堆(大堆)
void HPPushBig(Heap* php, HPDataType x)
{
assert(php);
//判断空间是否足够
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fali!!!");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;//因为数据还没有向上调整,所以size不能++
//向上调整
AdjustUpBig(php->arr, php->size++);
}
//数据的向下调整(大堆)
void AdjustDownBig(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;//求该父节点的左孩子
while (child < n)//该左孩子在数组内
{
if (child + 1 < n && arr[child] < arr[child + 1])//比较左右孩子大小
{
child++;
}
//父节点和孩子结点交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//出堆(大堆)
void HPPopBig(Heap* php)
{
assert(php && php->size);//有效数据不能为零
//头尾数据交换
Swap(&php->arr[0], &php->arr[php->size - 1]);
//出堆
php->size--;
//数据的向下调整
AdjustDownBig(php->arr, 0, php->size);
}
2、堆排序
1)排降序
以排降序为例,通过建小堆来实现堆排序
void HeapSortSmall(int* arr, int n)
{
建小堆,向上排序法建堆
//for (int i = 0; i < n; i++)
//{
// //向上建堆
// AdjustUpSmall(arr, i);
//}
//向下排序,建小堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//(n - 1 - 1) / 2 求的是最底下子结点的父结点
{
AdjustDownSmall(arr, i, n);
}
//排降序
int end = n - 1;
while (end > 0)//出堆
{
Swap(&arr[0], &arr[end]);
AdjustDownSmall(arr, 0, end);
end--;
}
}
2)排升序
同理,通过建立大堆排升序
void HeapSortBig(int* arr, int n)
{
//向下排序,建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//(n - 1 - 1) / 2 求的是最底下子结点的父结点
{
AdjustDownBig(arr, i, n);
}
//排升序
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDownBig(arr, 0, end);
end--;
}
}
3、Top-K问题
- 问:有一个数据量特别大的集合,如何用一个最多包含 K 个元素的集合来求得最大或最小的前 K 个元素
- 答:用前 K 个元素建大堆或小堆,在用剩余的N-K个元素依此与堆顶元素比较大小,如过该元素比堆顶元素大于或小于,则替换掉堆顶元素。
//造数据
void CreateNDate()
{
int n = 100000;
srand((unsigned int)time(NULL));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen fail!!!");
return;
}
for (int i = 0; i < n; i++)
{
int x = (rand() + i) % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
//Top-K 问题
void TopK()
{
int k = 10;
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen fail!!!");
exit(1);
}
int minHeap[10] = { 0 };
//从文件中读前K个数据
for (int i = 0; i < k; i++)
{
fscanf(fout, "&d\n", minHeap);
}
//建小堆
for (int i = (k - 1 - 1) / 2; i > 0; i--)
{
AdjustDownSmall(minHeap, 0, k);
}
//继续取文件的数据,将读取到的数据和堆顶进行比较
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minHeap[0])
{
minHeap[0] = x;
AdjustDownSmall(minHeap, 0, k);
}
}
//排升序
printf("排升序:");
HeapSortBig(minHeap, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", minHeap[i]);
}
printf("\n");
fclose(fout);
}
四、总代码
Heap.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<time.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}Heap;
//堆的初始化
void HPInit(Heap* php);
//堆的销毁
void HPDestroy(Heap* php);
//两个数据的交换
void Swap(HPDataType* x, HPDataType* y);
//数据的向上调整(小堆)
void AdjustUpSmall(HPDataType* arr, int child);
//入堆(小堆)
void HPPushSmall(Heap* php, HPDataType x);
//数据的向下调整(小堆)
void AdjustDownSmall(HPDataType* arr, int parent, int n);
//出堆(小堆)
void HPPopSmall(Heap* php);
//判空
bool HPEmpty(Heap* php);
//取堆顶数据
HPDataType HPTop(Heap* php);
//数据的向上调整(大堆)
void AdjustUpBig(HPDataType* arr, int child);
//入堆(大堆)
void HPPushBig(Heap* php, HPDataType x);
//数据的向下调整(大堆)
void AdjustDownBig(HPDataType* arr, int parent, int n);
//出堆(大堆)
void HPPopBig(Heap* php);
//堆排序
//排升序,建大堆
//排降序,建小堆
void HeapSortSmall(int* arr, int n);
void HeapSortBig(int* arr, int n);
Heap.cpp
#include"Heap.h"
//堆的初始化
void HPInit(Heap* php)
{
assert(php);
php->arr = NULL;
php->capacity = php->size = NULL;
}
//堆的销毁
void HPDestroy(Heap* php)
{
assert(php);
if (php->arr)
{
free(php->arr);
}
php->arr = NULL;
php->capacity = php->size = NULL;
}
//两个数据的交换
void Swap(HPDataType* x, HPDataType* y)
{
assert(x || y);
HPDataType cpy = *x;
*x = *y;
*y = cpy;
}
//数据的向上调整(小堆)
void AdjustUpSmall(HPDataType* arr, int child)
{
assert(arr);
int parent = (child - 1) / 2;//求该子节点的父结点
while (child > 0)
{
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//入堆(小堆)
void HPPushSmall(Heap* php, HPDataType x)
{
assert(php);
//判断空间是否足够
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr,sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fali!!!");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;//因为数据还没有向上调整,所以size不能++
//向上调整
AdjustUpSmall(php->arr, php->size++);
}
//数据的向下调整(小堆)
void AdjustDownSmall(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;//求该父节点的左孩子
while (child < n)//该左孩子在数组内
{
if (child + 1 < n && arr[child] > arr[child + 1])//比较左右孩子大小
{
child++;
}
//父节点和孩子结点交换
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//出堆(小堆)
void HPPopSmall(Heap* php)
{
assert(php && php->size);//有效数据不能为零
//头尾数据交换
Swap(&php->arr[0], &php->arr[php->size - 1]);
//出堆
php->size--;
//数据的向下调整
AdjustDownSmall(php->arr, 0, php->size);
}
//判空
bool HPEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
//取堆顶数据
HPDataType HPTop(Heap* php)
{
assert(php && php->size);
return php->arr[0];
}
//数据的向上调整(大堆)
void AdjustUpBig(HPDataType* arr, int child)
{
assert(arr);
int parent = (child - 1) / 2;//求该子节点的父结点
while (child > 0)
{
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//入堆(大堆)
void HPPushBig(Heap* php, HPDataType x)
{
assert(php);
//判断空间是否足够
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fali!!!");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;//因为数据还没有向上调整,所以size不能++
//向上调整
AdjustUpBig(php->arr, php->size++);
}
//数据的向下调整(大堆)
void AdjustDownBig(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;//求该父节点的左孩子
while (child < n)//该左孩子在数组内
{
if (child + 1 < n && arr[child] < arr[child + 1])//比较左右孩子大小
{
child++;
}
//父节点和孩子结点交换
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//出堆(大堆)
void HPPopBig(Heap* php)
{
assert(php && php->size);//有效数据不能为零
//头尾数据交换
Swap(&php->arr[0], &php->arr[php->size - 1]);
//出堆
php->size--;
//数据的向下调整
AdjustDownBig(php->arr, 0, php->size);
}
//堆排序
//排升序,建大堆
//排降序,建小堆
void HeapSortSmall(int* arr, int n)
{
建小堆,向上排序法建堆
//for (int i = 0; i < n; i++)
//{
// //向上建堆
// AdjustUpSmall(arr, i);
//}
//向下排序
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//(n - 1 - 1) / 2 求的是最底下子结点的父结点
{
AdjustDownSmall(arr, i, n);
}
//排降序
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDownSmall(arr, 0, end);
end--;
}
}
void HeapSortBig(int* arr, int n)
{
//向下排序
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//(n - 1 - 1) / 2 求的是最底下子结点的父结点
{
AdjustDownBig(arr, i, n);
}
//排升序
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDownBig(arr, 0, end);
end--;
}
}
main.cpp
#include"Heap.h"
//造数据
void CreateNDate()
{
int n = 100000;
srand((unsigned int)time(NULL));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen fail!!!");
return;
}
for (int i = 0; i < n; i++)
{
int x = (rand() + i) % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
//Top-K 问题
void TopK()
{
int k = 10;
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen fail!!!");
exit(1);
}
int minHeap[10] = { 0 };
//从文件中读前K个数据
for (int i = 0; i < k; i++)
{
fscanf(fout, "&d\n", minHeap);
}
//建小堆
for (int i = (k - 1 - 1) / 2; i > 0; i--)
{
AdjustDownSmall(minHeap, 0, k);
}
//继续取文件的数据,将读取到的数据和堆顶进行比较
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
if (x > minHeap[0])
{
minHeap[0] = x;
AdjustDownSmall(minHeap, 0, k);
}
}
//排升序
printf("排升序:");
HeapSortBig(minHeap, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", minHeap[i]);
}
printf("\n");
fclose(fout);
}
void test01()
{
Heap hpsmall;
//堆的初始化
HPInit(&hpsmall);
//入堆(小堆)
HPDataType arr[10] = { 32,11,55,223,86,123,867,3423,75,13 };
for (int i = 0; i < 10; i++)
{
HPPushSmall(&hpsmall, arr[i]);
}
//取堆顶数据
printf("Heap: ");
while (!HPEmpty(&hpsmall))
{
printf("%d ", HPTop(&hpsmall));
//出堆(小堆)
HPPopSmall(&hpsmall);
}
printf("\n");
//堆的销毁
HPDestroy(&hpsmall);
//排降序
printf("排降序:");
HeapSortSmall(arr, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
//排升序
printf("排升序:");
HeapSortBig(arr, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
//CreateNDate();
TopK();
}
int main()
{
test01();
return 0;
}