一、堆排序
堆排序 即 利用大堆、小堆的性质 ,从而排出 升序或者降序。
1.思路
总共分为两个步骤:
1.1建堆
排升序,建大堆
排降序,建小堆
1.2利用堆删除的思想进行排序
以排升序 ,建大堆 为例
step1.交换首尾
(此时栈顶最大元素被交换到尾部 并且尾部固定不再变动)
step2.将新的栈顶元素向下调整 从而 满足大堆的特性
(完成调整,此时的栈顶是次大的元素)
重复上述步骤,过程中尾部一直向头部靠近 直到首尾相同
2.代码实现
#include<stdio.h>
void Swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//建大堆 向上调整
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;
}
}
}
//向下调整
void AdjustDown(int* a, int size, int parent)
{
int child = 2 * parent + 1;
while (child < size)
{
//假设法 找出较大孩子 默认左孩子更大 如果不是 更新一下
if (child+1<size &&a[child] < a[child + 1])
{
child++;
}
//如果 父结点 小于 较大孩子 交换、
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
//更新 父节点 孩子节点下标
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
//对数组进行堆排序
void HeapSort(int* a, int n)
{
//手动建堆
for (int i = 0; i < n; i++)
{
AdjustUp(a, i);
}
//首尾元素交换
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
//新的栈顶元素 不满足大堆特性 向下调整
AdjustDown(a, end, 0);
end--;
}
}
int main()
{
int a[] = { 4,7,9,10,6,3,5,0,1 };
HeapSort(a, sizeof(a) / sizeof(int));
for (int i = 0; i < sizeof(a)/sizeof(int); i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
3.运行测试结果
二、top-k问题
问题描述
TOP-K问题:在数据量比较大情况下,求前k个最大值或最小值
基本思路
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
关键点理解:
如果找最大元素 为什么需要建的是小堆?
答:如果找最大元素 使用大堆 假设此时最大的元素已经被替换进堆顶 ,那么接下来 次大的元素 无法进入堆 !所以用大堆的话 只能找出最大的一个元素 后面次大的k-1个元素 无法找出!
再来理清 用小堆 找前k个最大元素 的思路:
比堆顶元素大 则替换堆顶元素 然后向下调整堆顶元素 从而满足小堆特性 这样大的数就会沉底 遍历完所有元素 次大的元素、最大的元素 会在k个元素的小堆里沉底,过程中小堆的元素是不断被替换的。
代码实现
注意:这里我们用CreateNData()函数 造10000个 小于1000000的随机数 ,并且存储在data.txt。生成随机数后 为了 测试PrintTopK()函数的功能 ,我们将data.txt 中的 随机五个数 手动改为 大于1000000的数 ,看能否把这五个改动的数找出来,以此完成测试!
#include<stdio.h>
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
void CreateNData()
{
int n = 10000;
srand(time(0));
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() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//调整为小堆
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;
}
}
}
void AdjustDown(int* a, int size, int parent)
{
int child = 2 * parent + 1;
while (child < size)
{
//假设法 找出较小孩子 默认左孩子更小 如果不是 更新一下
if (child + 1 < size && a[child] > a[child + 1])
{
child++;
}
//如果 父结点 大于 较小孩子 交换、
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
//更新 父节点 孩子节点下标
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void PrintTopK(int k)
{
//造一个存有10000个数据的文件
//CreateNData();
//打开文件
FILE* fout = fopen("data.txt", "r");
if (fout == NULL)
{
perror("fopen fail");
return;
}
//开辟k个空间
int* a = (int*)malloc(sizeof(int) * k);
//依次从文件中读取数据到k个空间里
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &a[i]);
}
//建一个包含k个数的小堆
for (int i = 0; i < k; i++)
{
AdjustUp(a, i);
}
//依次读取文件里的数据 直到读取失败返回EOF
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
//如果比根节点 大 则替换
if (x > a[0])
{
a[0] = x;
}
//向下调整 根节点元素 使其满足小堆特性
AdjustDown(a, k, 0);
}
for (int i = 0; i < k; i++)
{
printf("%d ", a[i]);
}
}
int main()
{
PrintTopK(5);
return 0;
}
测试运行结果