堆《数据结构》
- 1. 堆排序
- 1.1 建堆
- 向上调整建堆
- 向下调整建堆
- 1.2 利用堆删除思想来进行排序
- 1.3Top-k问题
- 2.堆的时间复杂度
1. 堆排序
1.1 建堆
建大堆
建小堆
向上调整建堆
AdjustUp建堆
void AdjustUp(HPDataType* a, int child)
{
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void headsport(int* a, int n)//建堆
{
for (int i = 0;i < n;i++)
{
AdjustUp(a, i);//建堆(降序建大堆)向上调整建堆
}
}
void test1()
{
int arr[] = {1,5,3,2,7,9,4,6,8};
headsport(arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
{
printf("%d", arr[i]);
}
}
1:我们通过AdjustUp函数来建堆
2:在控制AdjustUp函数中a[child] < a[parent]的大小比较关系,这是控制建小堆,还是建大堆
如:a[child] < a[parent] 建小堆
a[child] > a[parent]建 大堆
向下调整建堆
AdjustDown
void AdjustDown(HPDataType* a, int n, int parent)
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // 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 headsport(int* arr, int n)
{
//时间复杂度O(N)
for (int i = (n-1-1)/2;i >=0;i--)
{
AdjustDown(arr,n,i);//向下调整建堆
}
}
void test1()
{
int arr[] = {1,5,3,2,7,9,4,6,8};
headsport(arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
{
printf("%d", arr[i]);
}
}
与AdjustUp同理:
arr[child]>arr[parent]->大堆
arr[child]<arr[parent]->小堆
1.2 利用堆删除思想来进行排序
1:建堆
升序:建大堆
降序:建小堆
2:建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
实现图:
void headsport(int* arr, int n)
{
for (int i = (n-1-1)/2;i >=0;i--)
{
AdjustDown(arr,n,i);//向下调整建堆
}
int end = n - 1;//取最后一位数据
while (end > 0)
{
Swap(&arr[0], &arr[end]);//交换
AdjustDown(arr, end, 0);//在向下建堆
end--;
}
}
void test1()
{
int arr[] = {1,5,3,2,7,9,4,6,8};
headsport(arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
{
printf("%d", arr[i]);
}
}
int main()
{
test1();
//CreateNdDate();
/*TestHeap();*/
/*TestHeap3();*/
return 0;
}
1.3Top-k问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据
量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1.用数据集合中前K个元素来建堆
·前k个最大的元素,则建小堆
·前k个最小的元素,则建大堆
2.用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
void CreateNdDate()
{
//造数据
int n = 100000;
srand(time(0));//随机种子
const char* file = "daf.txt";
FILE* fin = fopen(file, "w");//打开文件
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0;i < n;i++)
{
int x = (rand() + i) % 1000000;//随机值
fprintf(fin, "%d\n", x);//写入数据
}
fclose(fin);
}
void TestHeap()
{
int k;//获取前k个数
printf("请输入k>");
scanf("%d", &k);
int* kminheap = (int*)malloc(sizeof(int) * k);
if (kminheap == NULL)
{
perror("mallco fail");
return;
}
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error ");
return;
}
//读取文件前k个数
for (int i = 0;i < k;i++)
{
fscanf(fout,"%d", &kminheap[i]);
}
for (int i = (k - 1 - 1) / 2;i >= 0;i--)//建小堆
{
AdjustDown(kminheap, k, i);
}
//读取剩下n-k个数
int x = 0;
while (fscanf(fout,"%d", &x)>0);
{
if (x > kminheap[0])
{
kminheap[0] = x;
AdjustDown(kminheap, k, 0);
}
}
printf("最大前k个\n");
for (int i = 0;i < k;i++)
{
printf("%d\n", kminheap[i]);
}
}
int main()
{
CreateNdDate();
TestHeap();
return 0;
}
2.堆的时间复杂度
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果):
向下调堆:从最后一个根节点开始调整
向上调堆:
调整次数:
第二层:向上调整 1次
第三层: 向上调整2次
…
…
第n层:向上调整n次
节点个数
第一层:2^0个
第二层:2^1个
…
…
第n层:2^n-1个
感谢大家观看!!!