堆:是一种特殊的完全二叉树,一般通过顺序表存储,分为大堆和小堆两类。
大堆:父节点的值恒大于子节点的值。
小堆:父节点的值恒小于子节点的值。
创建堆,可以使得根节点成为整个堆中保存最大或最小的值的结,根据这个特性,堆可以用于排序和解决TopK问题。
创建堆:
方法一:向上排序法
原理:对由前k个结点构成的堆,插入第k+1个结点到最后,然后不断与其父节点进行比对,符合条件就互换,不符合条件就留在原地结束循环。循环结束后,前k+1个结点仍是堆。
时间复杂度:O(nlogn)
方法二:向下排序
原理:首先创造一个完全二叉树,然后从倒数第二层最后一个结点开始,不断以当前结点为根节点创建堆,直至最后以根节点创建堆。每次创建堆,都是先比较该结点的子节点,找出更可能上移的结点,然后与父节点比较,符合条件就互换,然后继续该步骤,不符合条件就停止循环。循环结束后,该部分就成为了一个堆。
时间复杂度:O(N)
堆排序:
思路:将根节点与最后一个结点互换,使得最后一个结点永远保存当前最大(小)的结点,然后对互换后的根节点进行向下排序,使得排完序列后整个二叉树仍然是堆,再将新的根节点与倒数第二个结点互换,重复上述操作。最后,整个堆就是一个升序或降序的顺序表。
时间复杂度:O(NlogN)
每个根节点最多移动logN次,一共N-1个结点进行移动,因此时间复杂度是NlogN。
具体代码实现:
void swap1(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void up_insert1(HPDataType* a, int i)
{int child = i;int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){swap1(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}
void down_insert1(HPDataType* a, int n, int i)
{int parent = i;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){swap1(&a[parent], &a[child]);parent = child;child = child * 2 + 1;}else{break;}}}
void HeapSort(int* a, int n)
{//创建堆for (int i = 1; i < n; i++){up_insert1(a, i);}测试//for (int i = 0; i < n; i++)//{// printf("%d ", a[i]);//}//printf("\n");//排序for (int i = 0; i < n; i++){swap1(&a[0], &a[n - 1-i]);down_insert1(a, n - i - 1, 0);}//测试for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");}
TopK问题:
问题要求:对N个数据,只取最大(或最小)的前k个。
思路:先用前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;fprintf(fin, "%d\n", x);}fclose(fin);
}
void swap1(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void down_insert1(HPDataType* a, int n, int i)
{int parent = i;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){swap1(&a[parent], &a[child]);parent = child;child = child * 2 + 1;}else{break;}}}
void PrintTopK(int k)
{const char* file = "data.txt";FILE* fin = fopen(file, "r");//创建小堆 (找最大的前k个)int* arr = (int*)malloc(sizeof(int) * k);if (arr == NULL){perror("malloc");exit(-1);}int i = 0;int num = k;while (num--){fscanf(fin, "%d\n", &arr[i]);i++;}Heap hp;HeapCreate(&hp, arr, k);//插入其余元素int x = 0;int count = 0;while (fscanf(fin, "%d\n", &x) != EOF){ /* if (count % 1000 == 0){x *= 1000;}*/if (x > hp.a[0]){hp.a[0] = x;down_insert1(hp.a, k, 0);}}printf("插入成功\n");//测试for (int j = 0; j < k; j++){printf("%d ",hp.a[j]);}printf("\n");}