前情提要:看本文需要能够熟练使用指针,否则你会看的一头雾水
废话不多说,直接看我制作的图片,我在图片中写了详细步骤
#include<stdio.h>
#define MAX 20
struct Heap//堆结构体
{
int num;//数组里当前有多少个元素
int capacity;//数组的容量
int* arry;//数组指针
};
void show(struct Heap* heap);
void creat_heap(struct Heap* heap);
int get_num(struct Heap* heap);
int adjust(int* num, int i, int lenth);
void add(struct Heap* heap);
//从最后一个元素开始和他的父亲比较,调整成最大堆,再依次往前,是的每个左右孩子和他的父节点构成最大堆
//最开始有两种情况:最后一个元素的位置是奇数和最后一个元素的位置是偶数
//右孩子一定是奇数,左孩子是偶数
void creat_heap(struct Heap* heap)//在数组里输入一串无序的数据,将其转换成最大堆
{
int* max;
int* arry = heap->arry;
printf("输入几个数据:");
scanf("%d", &heap->num);
for (int i = 1; i <= heap->num; i++)
scanf("%d", arry + i);
for (int i = heap->num; i > 1;)
{
if (i % 2)//最后一个元素在奇数位置,是右孩子
{
int flag = arry[i] > arry[i - 1] ? i : i - 1;
max = arry[i] > arry[i - 1] ? (arry + i) : (arry + i - 1);
if (*max > arry[i / 2])
{
int add = 0;
int charge = arry[i / 2];
arry[i / 2] = *max;
*max = charge;
while (1)
{
int judge = adjust(max + add, flag + add, heap->num);//孩子节点和父节点调换之后,还要在向下比较,看是否是最大堆
add += judge; //如果不是最大堆,则向下调整
if (!judge)
break;
}
}
i -= 2;
}
else//最后一个元素在奇数位置,是左孩子
{
if (arry[i] > arry[i / 2])
{
int charge = arry[i / 2];
arry[i / 2] = arry[i];
arry[i] = charge;
}
i--;
}
}
}
int get_num(struct Heap* heap)
{
int val = heap->arry[1], lenth = heap->num;
int* num = heap->arry + 1;
heap->arry[1] = heap->arry[heap->num--];
for (int i = 1; i < lenth;)
{
if (lenth >= 2 * i + 1)//被交换的孩子有左孩子和右孩子
{
//这里的数据要特别注意,数组里是偏移地址一个节点的左右孩子就是偏移他现在的位置和加一
int* max = num[i] > num[i + 1] ? (num + i) : (num + i + 1);
int flag = num[i] > num[i + 1] ? (2 * i) : (2 * i + 1);//数组里是偏移量而flag记录是真正的位置,所以是原节点乘2
if (*max < * num)
{
return val;
}
int charge = *max;
*max = *num;
*num = charge;
num = max;
i = flag;
}
else if (lenth >= 2 * i)
{
if (*(num + 2 * i) > * num)
{
int charge = *num;
*num = *(num + 2 * i);
*(num + 2 * i) = charge;
}
return val;
}
else
{
return val;
}
}
return val;
}
int adjust(int* num, int i, int lenth)//父亲节点和孩子节点交换数据后,看被交换的孩子节点的新数据是否和他的孩子构成最大堆
{
if (lenth >= 2 * i + 1)//被交换的孩子有左孩子和右孩子
{
int* max = num[2 * i - i] > num[2 * i + 1 - i] ? (num + 2 * i - i) : (num + 2 * i + 1 - i);
int flag = num[2 * i - i] > num[2 * i + 1 - i] ? (2 * i - i) : (2 * i + 1 - i);
//减i的原因是数组里的数字是地址的偏移量,传进来的是num的地址就是原数组偏移i后的地址
if (*max > * num)
{
int charge = *max;
*max = *num;
*num = charge;
return flag;
}
}
else if (lenth >= 2 * i)
{
if ( * (num + 2 * i - i)> * num )
{
int charge = *num;
*num = *(num + 2 * i - i);
*(num + 2 * i - i) = charge;
return (2 * i-i);
}
}
return 0;
}
void add(struct Heap* heap)
{
if (heap->capacity - 1 == heap->num)//当已有元素数等于数组容量时需要扩容
{
int* p = malloc(sizeof(struct Heap) * heap->capacity + 20);
for (int i = 0; i < heap->capacity; i++)
{
p[i] = heap->arry[i];
}
heap->arry = p;
heap->capacity += 20;
}
int num, * arry = heap->arry, * max;
printf("请输入要插入的数据:");
scanf("%d", &num);
heap->arry[++heap->num] = num;//把新元素插在数组尾部,然后和他的父节点比较向上调整
for (int i = heap->num; i > 1;)
{
if (i % 2)//最后一个元素在奇数位置,是右孩子
{
int flag = arry[i] > arry[i - 1] ? i : i - 1;
max = arry[i] > arry[i - 1] ? (arry + i) : (arry + i - 1);
if (*max > arry[i / 2])//交换后的孩子不需要向下比较,因为原本就是最大堆,原父节点换下来后肯定还是最大值依旧构成最大堆
{
int add = 0;
int charge = arry[i / 2];
arry[i / 2] = *max;
*max = charge;
}
}
else//最后一个元素在奇数位置,是左孩子
{
if (arry[i] > arry[i / 2])
{
int charge = arry[i / 2];
arry[i / 2] = arry[i];
arry[i] = charge;
}
}
i /= 2;
}
}
void show(struct Heap* heap)
{
printf("当前堆数据:");
for (int i = 1; i <= heap->num; i++)
printf("%d ", *(heap->arry + i));
printf("\n");
}//55 66 30 49 9 79 72 43 38 83 87 91
int main()
{
struct Heap heap;
heap.capacity = MAX;
heap.arry = malloc(sizeof(int) * MAX);
heap.arry[0] = 100;//数组中第一个元素写一个最大数(其实也没什么用,不用纠结),真正的数据从a[1]开始存储
creat_heap(&heap);
show(&heap);
add(&heap);
show(&heap);
printf("取出一个堆顶数据:%d\n", get_num(&heap));
show(&heap);
}
下面展示一个在vs2019下运行的案例
我把最后的结果画出来了,方便大家更直观的看到运行结果
大顶堆分享结束,掌握大顶堆后,些小顶堆自然不是问题。发现bug提醒博主哦^ _ ^