一、堆的实现
堆的性质:
-
堆中某个结点的值总是不大于或不小于其父结点的值;
-
堆总是一棵完全二叉树
代码如下
//这里建立的是小堆---即所有孩子节点的值小于父节点的值
//大堆的代码与这个类似,留给读者思考
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap {
HPDataType* a;
int size;
int capacity;
}HP;
//初始化
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);
//向下调整堆
void AdjustDown(HPDataType* a, int n, int parent);
//向上调整堆
void AdjustUp(HPDataType* a, int child);
//交换
void Swap(HPDataType* p1, HPDataType* p2);
//初始化
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 = php->capacity = 0;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整堆
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)//孩子节点到根节点
{
if (a[child] < a[parent])//小堆
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整堆
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child+1] < a[child])
child++;
if (a[child] < a[parent])//小堆
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size++] = x;
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;
}
1.1这里面有两个函数比较重要也比较难理解---AdjustUp函数和AdjustDown函数
二、堆排序
基本思想:先建立一个堆,根据堆的性质,堆顶的元素是最大值或最小值,那么我们只要将堆顶的元素与数组的最后一个元素交换,然后根据HeapPop函数的思路,调整前面的数据使得它还是一个堆,如此循环,得到一个有序的序列
void HeapSort(int*a,int n)
{
//建立堆
//...
//调整堆
for(int i=n-1;i>0;i--)//只要调整n-1次
{
Swap(&a[0],&a[i]);
AdjustDown(a,i,0);
}
}
2.1这里建立堆有两种思路,分别是AdjustUp和AdjustDown,即向上调整和向下调整
代码如下
void HeapSort(int*a,int n)
{
//建立堆
//AdjustUp
//for(int i=1;i<n;i++)//从第二个元素开始
// AdjustUp(a,i);
//AdjustDown
for(int i=(n-1-1)/2;i>=0;i--)//从最后一个叶子节点的父节点开始
AdjustDown(a,n,i);
//调整堆
for(int i=n-1;i>0;i--)//只要调整n-1次
{
Swap(&a[0],&a[i]);
AdjustDown(a,i,0);
}
}
2.1.1上面两种建堆的时间复杂度分别时多少?
2.2Top-K问题(在大量的数据中找到前k个最大值或最小值)
基本思路:假设在n个数据中找前k个最大值,先建一个大小为k的小堆(反之,建大堆),然后依次比较堆顶元素和n-k个数据,不断调整小堆,最后堆中存放的就是前k个最大值,但不有序
void CreateNDate()
{
//造数据
int n = 10000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
//将数据放入文件中
for (size_t i = 0; i < n; ++i)
{
int x = rand() % 1000000;//数据在0~1000000
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void PrintTopK(int k)
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return;
}
int* tmp = (int*)malloc(sizeof(int) * k);
//读取数据
for (int i = 0; i < k; i++)
{
fscanf(pf, "%d", &tmp[i]);
}
//建立小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(tmp, k, i);
}
//将堆顶元素和其他元素比较,如果大于则交换,调整堆
while (!feof(pf))
{
int val = 0;
fscanf(pf, "%d", &val);
if (val > tmp[0])
{
tmp[0] = val;
AdjustDown(tmp, k, 0);
}
}
for (int i = 0; i < k; i++)
printf("%d ",tmp[i]);
free(tmp);
}
int main()
{
int k = 10;
//CreateNDate();//这个函数只要调用一次
PrintTopK(k);
return 0;
}
如果觉得对你有帮助的话,不要忘记点赞、评论哦!!!