1、堆的概念
在数据结构中,堆在逻辑上可以理解为一颗完全二叉树,而在物理结构上普遍用数组表示。
比如:
而在堆中又分为两种
- 小堆:在逻辑结构中,除叶结点任意节点都小于其子节点的值,称作小堆。
- 大堆:在逻辑结构中,除叶结点任意节点都大于其子节点的值,称作大堆。
从堆的下标关系来看,如果父节点,那么与其左子节点关系固定为parent2+1,右子节点关系固定为parent2+2。
(12+1=3,12+2=4)
而不管是左子节点还是右子节点,parent = (child-1)/2; ( (3-1) / 2 = 1,(4-1) / 2 = 1)
2、堆的调整算法
2.1、向上调整算法
如果要实现一个数组里的数组,从下标为0位置开始依次进堆的操作。
以下以建小堆为例。
中间过程
这样从第一个数据开始,到入堆最后一个数据,每个数据都通过与父节点比较,确保满足建小堆条件。
代码实现
void AdjustUp(int* 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;
}
}
}
时间复杂度计算:
2.3、向下调整算法
向下调整算法有一个前提就是,只有当左右子树为堆的时候,才能使用。
比如,以87为根的左右子树都满足小堆条件,那么这个时候就可以用向下调整算法。
将父节点(如:87)和左右子节点小的那个交换(比如21),一直重复这个操作,直至到达叶子结点。
如果要删除堆顶的数据,该怎么办?
如果根节点左右子树不为堆怎么办,一直向下找到满足左右子树为堆的根(保底找到为左右子节点为叶节点的根节点,因为叶结点满足小堆的条件)。
堆排序中堆的建立
时间复杂度计算:
所以和向上调整算法相比,向下调整算法的时间复杂度要更高效。
3、TopK问题
TopK问题就是从N个数里面,找出前K个最大的数。
有两种方法
- 通过堆排序,再挑取前面K个数。时间复杂度O(N*LogN)空间复杂度O(N)
- 通过堆选数:
a. 建大堆:建好大堆,每次取出堆顶数据,再调整成大堆。时间复杂度O(N+K*LogN)空间复杂度O(N)
b. 建小堆:对前K个数建好小堆,再对N-K个数进行筛选,如果数大于堆顶数据,那么进堆并向下调整,最后小堆里的数就是前K个最大的数。时间复杂度O(K+(N-k)LogK),当N远大K就是O(N),空间复杂度O(K)
以上三种方法,值得注意的是,当数据量很大时,第一种方法和第二种方法中的建大堆,就会因为空间的问题难以进行,比如N达到100亿,也就需要40GB的内存去储存数据,而建小堆会很有效的避免这个问题。
代码实现以及测试:
void CreatFile(int size)
{
FILE* fin = fopen("nums.txt", "w");
if (fin == NULL)
{
perror("fopen fail");
return -1;
}
int i = 0;
for (i = 0; i < size; i++)
{
fprintf(fin, "%d\n", rand() % 100000);
}
fclose(fin);
}
int main()
{
//写入大量数据到文件
//srand(time(NULL));
int size = 1000000;
/*CreatFile(size);*/
//前k个建立小堆
int k = 10;
int* a = (int*)malloc(sizeof(int) * k);
int i = 0;
FILE* fout = fopen("nums.txt", "r");
//从文件读取K个
for (i = 0; i < k; i++)
{
fscanf(fout, "%d", &a[i]);
}
//读取后进行向下调整
for (i = (k - 2) / 2; i >= 0; i--)
{
AdjustDown(a, k, i);
}
//N-K进小堆比较
int val = 0;
while (fscanf(fout, "%d", &val) != EOF)
{
if (val > a[0])
{
Swap(&val, &a[0]);
AdjustDown(a, k, 0);
}
}
//打印
for (i = 0; i < k; i++)
{
printf("%d ", a[i]);
}
printf("\n");
fclose(fout);
return 0;
}
因为是随机数据写入文件,为了确定结果是我们要的前K个最大的,我们在文件中自己输入10个最大的数,这样在打印后能更清楚。
输出结果