1. 堆的概念及结构
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。
堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆属性:
parent = (child-1)/ 2
lchild = parent*2 + 1
rchild = parent*2 + 2
父节点小于子节点的称则称为小堆,父节点大于子节点的称为大堆。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2. 堆的基本实现
首先创建头文件 - Heap.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HeapDataType;
typedef struct Heap{
HeapDataType *a;
int size;
int capacity;
}HP;
然后实现基本操作:
//初始化堆
void HeapInit(HP*php){
assert(php);
php->a=NULL;
php->size=php->capacity=0;
}
//交换数据
void swap(HeapDataType*a,int parent,int child){
int temp=a[parent];
a[parent]=a[child];
a[child]=temp;
}
void AdjustUp(HeapDataType*a,int size){
//该算法逻辑思想是二叉树,物理中操作思想是数组
int child=size;
int parent;
for(parent=(child-1)/2;child>0; parent=(parent-1)/2){ //向上调整
if(a[parent]>=a[child]){
break;
}
swap(a,parent,child);
child=parent;
}
}
//插入
void HeapPush(HP*php,HeapDataType x){
assert(php);
int child,parent,new_capacity;
if(php->size==php->capacity){
new_capacity ==0 ? 4 : php->capacity*2;
HeapDataType *tmp=(int*)realloc(php->a,sizeof(int)*new_capacity);
if(tmp==NULL){
perror("realloc");
exit(-1);
}
php->a=tmp;
php->capacity=new_capacity;
}
php->a[php->size]=x;
AdjustUp(php->a,php->size);
++php->size;
}
//销毁
void HeapDestroy(HP*php){
assert(php);
free(php->a);
php->a=NULL;
php->size=php->capacity=0;
}
//向下调整
void AdjustDown(HeapDataType*a,int s,int length){
int parent=s,child;
for(child=parent*2+1;child<length;child=child*2+1){
int rc=a[parent];
if(child+1<length&&a[child]<a[child+1]){ //找出左右子树中较小数
child++;
}
if(rc>=a[child]){ //与父节点进行比较
break;
}
a[parent]=a[child]; //交换数据
a[child]=rc;
parent=child; //直到叶子结点
}
}
//删除最小/最大的数据
void HeapPop(HP*php){
assert(php);
if(php->size==0){
return;
}
swap(php->a,0,php->size-1); //交换第一个和最后一个
php->size--; //长度减一
AdjustDown(php->a,0,php->size); //向下调整
}
//打印
void HeapPrint(HP*php){
assert(php);
for(int i=0;i<php->size;i++){
printf("%d ",php->a[i]);
}
}
//判空
bool HeapEmpty(HP*php){
assert(php);
return php->size==0;
}
//获取堆顶数据
HeapDataType HeapTop(HP*php){
assert(php);
if(php->size==0){
return -1;
}
return php->a[0];
}
//返回堆中元素个数
int HeapSize(HP*php){
assert(php);
return php->size;
}
//堆排序
void HeapSort(int*a,int length){
HP php;
HeapInit(&php);
for(int i=0;i<length;i++){
HeapPush(&php,a[i]);
}
int j=0;
while(!HeapEmpty(&php)){
a[j]=HeapTop(&php);
HeapPop(&php);
j++;
}
}
那么在这些操作中比较重要的有三个:
1. 向上调整
void AdjustUp(HeapDataType*a,int size){
//该算法逻辑思想是二叉树,物理中操作思想是数组
int child=size;
int parent;
for(parent=(child-1)/2;child>0; parent=(parent-1)/2){ //向上调整
if(a[parent]>=a[child]){
break;
}
swap(a,parent,child);
child=parent;
}
}
当我们在堆中插入一个元素,在实际操作中相当于在数组尾部插入一个元素,但是不仅仅是插入一个元素在数组尾部,同时我们要满足特定的要求,那这时我们就需要利用完全二叉树用数组实现的一些特点:parent = (child-1)/ 2 。
实现步骤:
1 . 当插入一个数以后,我们首先要做的是要判断此位置是否是正确的位置。
2. 如果当前位置符合则结束,如果不符合则需要进行交换数据。
3. 进行迭代操作,重复上述操作,直到条件终止。
由于是从底部向上的所以被称为向上调整
2. 向下调整
void AdjustDown(HeapDataType*a,int s,int length){
int parent=s,child;
for(child=parent*2+1;child<length;child=child*2+1){
int rc=a[parent];
if(child+1<length&&a[child]<a[child+1]){ //找出左右子树中较小数
child++;
}
if(rc>=a[child]){ //与父节点进行比较
break;
}
a[parent]=a[child]; //交换数据
a[child]=rc;
parent=child; //直到叶子结点
}
}
如果理解了向上调整,向下调整其实也是很容易理解的。
实现步骤:
1 . 首先我们要找到该父亲结点的左子树,然后令左子树和右子树进行比较,根据需求选取(child是否++)。
2. 判断此位置是否满足堆的特点,如果满足则退出,不满足则交换。
3. 进行迭代操作,重复上述操作,直到条件终止。
由于此操作是由上到下,所有被称为向下调整。
3. 排序
void HeapSort(int*a,int length){
HP php;
HeapInit(&php);
for(int i=0;i<length;i++){
HeapPush(&php,a[i]);
}
int j=0;
while(!HeapEmpty(&php)){
a[j]=HeapTop(&php);
HeapPop(&php);
j++;
}
}
实现步骤:
1. 初始化堆
2. 将传入的数组赋值在堆中
3. 利用堆的堆顶是最大/最小值 来进行排序,每获取一次就要重新建堆,直到堆中元素为空。
此排序的空间复杂度是 0(n),那我们有没有什么办法让其空间复杂度是0(1)呢?
这个问题我们下期再见!

本文详细介绍了堆的概念,包括小堆和大堆,并展示了如何用C语言实现堆的基本操作,如初始化、插入、删除、调整和排序。重点讨论了向上调整和向下调整的过程,以及堆排序的实现步骤。堆排序通过初始化堆,将数组元素逐个插入堆中,然后不断取出堆顶元素,达到排序的目的。

被折叠的 条评论
为什么被折叠?



