一、堆的概念
1.1大堆和小堆
1.2堆的性质:
二、堆的向上调整
堆一般采用数组的形式来实现。在建堆的时候,通常有两种建堆方式,一种是利用堆的向上调整建堆,一种是利用堆的向下调整建堆,两种方法在时间复杂度上有本质区别,以下会进行推导。
2.1向上调整的过程
向上调整的过程是:如果子节点大于父节点就交换值,并更新子节点的位置为当前的父亲节点,更新父亲节点的位置为(子节点-1)/ 2,如果小于就不用调整。
2.2向上调整的条件
向上调整的条件是:除了插入的节点,其他节点都构成堆。
2.3堆的插入
堆的向上调整一般用来插入节点,即在堆的末尾插入一个节点,再向上调整。
2.4向上调整建堆的时间复杂度
向上调整在建堆时是采用逐一尾插并调整来完成建堆的。
2.5代码实现
//除了child,其他的都是堆
void AdjustUpMax(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;
}
}
}
三、堆的向下调整
堆的向下调整一般用来删除堆顶元素、建堆、排序
3.1堆的向下调整过程
父节点与最大的子节点比较,小于子节点就交换,更新父节点的位置为当前子节点的位置,更新子节点的位置为父节点*2+1,如果大于就不用调整,直到达到叶节点。
3.2向下调整的条件
必须保证左右子树都是大堆或者小堆。
3.3堆顶的删除
3.4向下调整建堆的时间复杂度
观察两种建堆方式的时间复杂度,可以看出向下调整建堆的效率会比向上调整高,通常采用向下调整来建堆。
(堆排序:排升序建大堆,排降序建小堆,具体见排序章节)
3.5代码实现
//左右子树都是大/小堆
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;
}
}
}
四、堆的TopK问题
4.1问题描述
即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大,以下我们建立文件存储数据来模拟该问题的实现。
4.2解决思路
1.如果求前K个最大元素,就建立小堆,此时堆顶一定是最小的元素,再让剩余的N-K个元素与堆顶一一比较,如果大于就替换,并向下调整堆,找到完成替换后的堆的最小堆顶元素,不断重复此过程,直到堆中的K个元素就是前K个最大元素
2.如果求前K个最小元素,就建立大堆,此时堆顶一定是最大的元素,再让剩余的N-K个元素与堆顶一一比较,如果小于就替换,并向下调整堆,找到完成替换后的堆的最大堆顶元素,不断重复此过程,直到堆中的K个元素就是前K个最小元素
4.3代码实现
模拟该问题需要两部分:
1.造数据:利用随机值造出10000个随机数据写入文件。
2.找到前K个元素:先读入文件的前K个元素,对这前K个元素建大堆或小堆(看问题需要),将剩余的N-K个元素与堆顶比较,看是否满足条件再替换,直到读完文件所有的数据后堆中的K个元素就是前K个最大或最小元素。
//TopK问题
void PrintTopK(const char*file, int k)
{
//建堆 用a中前k个元素建小堆
int* topk = (int*)malloc(sizeof(int) * k);
assert(topk);
//以读的形式打开文件
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error\n");
return;
}
//读前k个数据
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &topk[i]);
}
//建小堆
for (int i = (k - 2) / 2; i >= 0; i--)
{
AdjustDownMin(topk, k, i);
}
//将剩余n-k个元素依次与堆顶元素交换,不满则则替换
int val = 0;
int ret = fscanf(fout, "%d", &val);
while (ret != EOF)
{
if (topk[0] < val)
{
topk[0] = val;
AdjustDownMin(topk, k, 0);
}
ret = fscanf(fout, "%d", &val);
}
//打印
for (int i = 0; i < k; i++)
{
printf("%d ", topk[i]);
}
printf("\n");
//关闭文件
free(topk);
fclose(fout);
}
void CreatNData()
{
//造数据
int n = 10000;
srand(time(0));
const char* file = "data.txt";
//以写的形式打开文件
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error\n");
return;
}
//写文件
for (int i = 0; i < n; i++)
{
int x = rand() % 10000;
fprintf(fin, "%d\n", x);
}
//关闭文件
fclose(fin);
}