目录
一.二叉树的顺序结构及堆的概念
1.二叉树的顺序结构
2.堆的概念及结构
(1)堆的概念
(2)堆的性质
二.堆的实现
1.初始化堆
2.销毁堆
3.打印堆
4.插入数据,并保持依旧为堆
5.删除堆顶的数据
6.判断堆是否为空
7.堆中数据个数
8.返回堆顶数据
三.堆总代码
1.Heap.h
2.Heap.c
3.Test.c
四.堆排序
堆排序HeapSort函数
五.TOP-K问题
前言:这个是二叉树的第二篇,主要用二叉树来实现堆和堆排序,后面还有一篇实现二叉树。
最后一篇->C语言【数据结构】二叉树实现_糖果雨滴a的博客-CSDN博客
在初始化之前,我们要先创建一个结构体,因为是用数组存的,所以这里类似于动态的顺序表。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
size_t size;
size_t capacity;
}HP;
创建之后就是初始化了 ,整个初始化与顺序表一样。
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;;
}
销毁也与顺序表一样。
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; ++i)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
这个前面与顺序表的尾插一样,但是后面因为要控制插入数据后,保持依旧为堆,所以我在这里又实现了一个函数 AdjustUp 用来向上调整新插入的数,控制保持插入数据会依旧为堆。
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void AdjustUp(HPDataType* a, size_t child)
{
size_t 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 HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = realloc(php->a ,sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
++php->size;
//向上调整,控制保持是一个堆
AdjustUp(php->a, php->size - 1);
}
这里主要说一下这个AdjustUp这个函数。
首先因为是堆,所以是父节点连接着子节点,我们插入一个数据相当于在原本的二叉树中插入了一个叶节点,这时我们需要把这个叶节点和它的父节点比较。
因此我们定义一个变量parent 作为它的父节点,因此根据父节点和子节点的关系,我们可以得到parent = (child - 1) / 2. 接下来我们通过一个while循环来判断是否需要向上调整该新插入的节点,首先while的条件就是child > 0,这个child就是我们插入的节点,因为它最多能调整到根节点的位置,所以循环条件为child > 0.
while循环里面就是判断该节点与其父节点的大小关系,因为原本就已经是小堆(或大堆),所以如果该节点的值小于(或大于)其父节点就向上调整,让该节点和其父节点交换,因为要多次用到交换,所以我们实现了一个新的函数Swap用来交换子节点和父节点。交换之后再让原本的子节点变成父节点(child = parent),让父节点变成这个新的子节点的父节点(parent = (child - 1) / 2).
如果子节点大于(或小于)其父节点,说明此时的二叉树已经调整成了小堆(或大堆),所以我们直接break。
这个删除与顺序表的删除有一些一样的地方,都是直接通过--size来进行删除的。
但是堆的删除需要在之前先交换堆顶(根节点)和最后一个子节点,然后删除最后一个。
这里也需要进行调整,不过这里不是插入中的向上调整,而是向下调整
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < size)
{
//1.选出左右孩子中小的那个
if (child + 1 < size && a[child + 1] < a[child])
{
++child;
}
//2.如果孩子小于父亲,则交换,并继续往下调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;
AdjustDown(php->a, php->size - 1, 0);
}
首先交换堆顶(根节点)和最后一个子节点,然后删除最后一个。
之后再调用AdjustDown(向下调整)函数去把这个新的根节点(原本的最后一个子节点)向下调整。
这里我们传的参数要比插入函数中向上调整的参数多。
首先我们定义两个变量parent和child,parent就是传来的root,而child可能有两个,我们先定义一个左孩子child = parent * 2 + 1。之后同样通过一个while循环,条件就是判断该父节点的子节点如果 这时我们需要判断左孩子和右孩子的大小关系,但是在此之前我们首先要判断是否有右孩子(child + 1 < size),如果满足,则说明有右孩子,这时再判断大小关系,如果要实现小堆(或大堆),那么如果右孩子小于(或大于)左孩子,就++child,让子节点变成这个更小(或更大)的子节点。 接下来就判断子节点和父节点的大小关系,如果子节点小于(或大于)父节点,就Swap子节点和父节点,然后让父节点的位置变成子节点的位置,这时值是交换过的,所以要改变这个位置,然后这个子节点再根据新的父节点而改变。 如果子节点大于(或小于)父节点就直接break,说明已经成为了一个新的小堆(或大堆)。 通过判断size是否等于0,来判断。 直接返回size的值,size的大小就是数据的个数。 先要判断一下堆中是否有数据,如果有就返回第1个根节点,即a[0]。 这里展示的是小堆,如果想要改成大堆,只需要在向上调整函数和向下调整函数中的比较子节点和父节点大小那改变一下>和<即可。 Swap函数 向上调整函数 向下调整函数 在这里我们需要用到之前写的Swap交换函数,AdjustUp向上调整函数和AdjustDown函数。 这里我们没有一个堆,我们只有一个数组,需要我们自己把这个数组转化为堆,再进行排序。 因此我们有两种方法,一种是向上调整建堆法(时间复杂度O(N * logN)), 另一种是向下调整建堆法(时间复杂度O(N)),根据时间复杂度来说,向下调整建堆法能好一点,但是差距也不是特别大。 首先说这个向上调整建堆法:因为向上调整是利用父节点和子节点交换,并且父节点是根据子节点得出的(parent = (child - 1) / 2)所以我们可以从数组中的第二个元素开始调用向上调整函数,一直到最后一个元素。 向下调整建堆法:不能直接让i从第一个元素开始,而是要从后往前用向下调整函数,因为只有从后往前,才能让前面调整过的都是小堆(或大堆),而从前往后不行。而叶子节点不需要调整,因为一个节点既可以看成小堆,也可以看成大堆,所以要从倒数的第一个非叶子节点开始调整,因此想要找到这个非叶子节点就通过最后一个节点来找,最后一个节点的父节点就是该非叶子节点。因为元素个数为n,所以最后一个节点为n-1,所以该父节点为(n-1 - 1) / 2,一直调整到第一个元素。 接下来就是排序了,建小堆和大堆分别为降序和升序。 在排序的过程中,我们采用的方法类似于堆中的删除,从后往前排序,直到只剩最后一个根节点。首先交换根节点和最后一个节点,然后让交换后的根节点(原最后一个节点)进行向下调整,调整完之后,此时根节点就已经作为降序或者升序中的最后一个元素,依次进行,即可完成。 首先我们在TestTopk中通过srand,rand来创建一些<1000000的随机数据,然后我们在随机让10个数据变成>1000000的数,用来测试该排序,如果结果为这些数的排序,就说明排序成功。 之后调用PrintTopK函数来具体实现排序,并测试排序结果。 首先要先开辟一个可以存k个元素的数组,然后让这个数组a的前k个元素存进去。然后根据需要得到的前最大(或最小)k个元素,来选择建小堆(或大堆)。在这里我们调用AdjustDown向下调整函数来建堆。 之后,我们要让剩余的n - k个元素依次和堆顶元素比较,如果大于(或小于)堆顶元素,就让堆顶元素和该元素交换,然后在对该进行向下调整,依次循环n - k次,去掉每一个最小的元素(即堆顶元素),然后向下调整,剩下的最后就是这个数组a中最大(最小)的k个元素了。 最后我们可以写个循环,来判断一下这个Top-K问题是否完成。 这篇关于堆的相关实现及排序就到这结束了,下一篇就是二叉树的最后一篇了(关于二叉树的链式结构的实现)。6.判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
7.堆中数据个数
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
8.返回堆顶数据
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
三.堆总代码
1.Heap.h
#pragma once
#include
2.Heap.c
#include "Heap.h"
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;;
}
void HeapDestory(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; ++i)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void AdjustUp(HPDataType* a, size_t child)
{
size_t 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 HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = realloc(php->a ,sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
++php->size;
//向上调整,控制保持是一个堆
AdjustUp(php->a, php->size - 1);
}
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < size)
{
//1.选出左右孩子中小的那个
if (child + 1 < size && a[child + 1] < a[child])
{
++child;
}
//2.如果孩子小于父亲,则交换,并继续往下调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;
AdjustDown(php->a, php->size - 1, 0);
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
3.Test.c
void TestHeap()
{
HP hp;
HeapInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 4);
HeapPush(&hp, 0);
HeapPush(&hp, 7);
HeapPush(&hp, 3);
HeapPush(&hp, 9);
HeapPrint(&hp);
HeapPop(&hp);
HeapPrint(&hp);
if (!HeapEmpty(&hp))
{
printf("堆不为空\n");
}
else
{
printf("堆为空\n");
}
printf("堆size:%d\n", HeapSize(&hp));
printf("堆顶数据:%d\n", HeapTop(&hp));
HeapDestory(&hp);
}
int main()
{
TestHeap();
return 0;
}
四.堆排序
void Swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//向上调整
void AdjustUp(int* a, size_t child)
{
size_t 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, size_t size, size_t root)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
堆排序HeapSort函数
//堆排序 O(N * logN)
void HeapSort(int* a, int n)
{
//向上调整--建堆 O(N * logN)
/*for (int i = 1; i < n; ++i)
{
AdjustUp(a, i);
}*/
//向下调整--建堆 O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
size_t end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
int main()
{
int a[] = { 4, 2, 7, 8, 5, 1, 0, 6 };
HeapSort(a, sizeof(a) / sizeof(int));
for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
五.TOP-K问题
void PrintTopK(int* a, int n, int k)
{
//1.建堆--用a中前k个元素建堆
int* kminHeap = (int*) malloc(sizeof(int) * k);
if (kminHeap == NULL)
{
printf("malloc fail\n");
}
for (int i = 0; i < k; ++i)
{
kminHeap[i] = a[i];
}
//建小堆
for (int j = (k - 1 - 1) / 2; j >= 0; --j)
{
AdjustDown(kminHeap, k, j);
}
//2.将剩余n-k个元素依次与堆顶元素交换,不满则替换
for (int i = k; i < n; ++i)
{
if (a[i] > kminHeap[0])
{
kminHeap[0] = a[i];
AdjustDown(kminHeap, k, 0);
}
}
for (int j = 0; j < k; ++j)
{
printf("%d ", kminHeap[j]);
}
printf("\n");
free(kminHeap);
}
void TestTopk()
{
int n = 10000;
int* a = (int*)malloc(sizeof(int) * n);
srand(time(0));
for (size_t i = 0; i < n; ++i)
{
a[i] = rand() % 1000000;
}
a[5] = 1000000 + 1;
a[1231] = 1000000 + 2;
a[531] = 1000000 + 3;
a[5121] = 1000000 + 4;
a[115] = 1000000 + 5;
a[2305] = 1000000 + 6;
a[99] = 1000000 + 7;
a[76] = 1000000 + 8;
a[423] = 1000000 + 9;
a[0] = 1000000 + 1000;
PrintTopK(a, n, 10);
}
int main()
{
TestTopk();
return 0;
}