前言
本次博客来讲解一下堆的数组实现,好吧还是会结合图例,让大家理解
堆的定义
什么是堆?
堆是一颗完全二叉树。它的性质是父节点一定大于或者一定小于子节点
每一个结点都要满足这个性质就是堆
堆的特性是堆顶的数据一定是最大或最小,最大为大堆,最小为小堆
看图
那我们如何实现堆呢?
我们可以注意堆是一个完全二叉树,我们可以使用一个数组来模拟完全二叉树
那么如何使用数组实现完全二叉树
使用数组实现完全二叉树
OK,首先我们可以通过数组下标,来确定节点,只要能够得到父子关系就可以遍历整个完全二叉树,看图吧
OK,那么咱么可不可以实现一下前序遍历呢
使用递归和非递归
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define N 10
//适用于满二叉树和完全二叉树
typedef struct BianryTree {int a[N];
}BT;
void InitBinaryTree(BT* bt,int cursize)
{if (cursize >= N)return;bt->a[cursize] = cursize;InitBinaryTree(bt,cursize*2+1);InitBinaryTree(bt,cursize*2+2);
}
void TraversalTree(BT* bt, int cursize)
{if (cursize >= N)return;printf("%d ", bt->a[cursize]);TraversalTree(bt, cursize * 2 + 1);TraversalTree(bt, cursize * 2 + 2);
}
int main()
{BT* bt = (BT*)malloc(sizeof(BT));InitBinaryTree(bt,0);TraversalTree(bt, 0);return 0;
}
上面是前序遍历属于递归遍历
接下来看非递归遍历,其实可以
void TraversalTree(BT* bt, int cursize)
{while (cursize < N)printf("%d ", bt[cursize++]);
}
只需改一改就好,这里相当于层序遍历
那么使用数组去实现完全二叉树是可行的
数组实现堆
我们先看看堆有哪些接口,然后一一实现
看一看头文件吧
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int HpDatatype;
typedef struct Heap {HpDatatype* a;int capacity;int size;
}HP;
void IintHeap(HP* hp);
void PushHeap(HP* hp, HpDatatype x);
void PopHeap(HP* hp);
HpDatatype TopHeap(HP* hp);
void DestroyHeap(HP* hp);
bool EmptyHeap(HP* hp);
int SizeHeap(HP* hp);
OK
先实现几个简单的 初始化,没什么好说的
void IintHeap(HP* hp)
{hp->a = NULL;hp->capacity = 0;hp->size = 0;
}
返回堆大小,也没有什么好说的
int SizeHeap(HP* hp)
{assert(hp);return hp->size;
}
返回堆顶
HpDatatype TopHeap(HP* hp)
{assert(hp);assert(hp->size > 0);return hp->a[0];
}
判空
bool EmptyHeap(HP* hp)
{assert(hp);return hp->size == 0;
}
销毁堆
void DestroyHeap(HP* hp)
{assert(hp);free(hp->a);hp->capacity = 0;hp->size = 0;
}
插入数据
void PushHeap(HP* hp, HpDatatype x)
{assert(hp);//检查容量if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HpDatatype* temp = (HpDatatype*)realloc(hp->a, sizeof(HpDatatype) * newcapacity);if (temp == NULL){printf("realloc fail\n");return;}else{hp->a=temp;hp->capacity = newcapacity;}}//向上调整法hp->a[hp->size++] = x;AdjustUp(hp->a,hp->size-1);
}
这个代码的逻辑是,每插入一个数据,对该数据进行向上调整法
那么向上调整法可以确保,每插入一个数据,该数组保持为堆
那么现在我们画图来理解什么是向上调整法
OK,下面是向上调整法
void AdjustUp(HpDatatype* a, int child)
{//这里的向上调整法是调整它的祖先while (child > 0){int parent = (child - 1) / 2;if (a[child] < a[parent]){int temp = a[child];a[child] = a[parent];a[parent] = temp;child = parent;}else{break;}}
}
删除一个数据
void PopHeap(HP* hp)
{assert(hp);assert(hp->a > 0);swap(&hp->a[0], &hp->a[hp->size - 1]);//向下调整法AdjustDown(hp->a, 0, hp->size);hp->size--;
}
我们要删除的是堆顶的数据
所以让第一个数据与最后一个数据交换,size--
此时,第一个节点的左子树和右子树都是堆,我们只要再调整一下根节点即可
这种调整叫做向下调整法
这里就不画图了,有点麻烦
直接看代码吧
void AdjustDown(HpDatatype* a, int start, int end)
{int parent = start;//假设法,假设左孩子更接近目的int child = parent * 2 + 1;end = end - 1;while (child <end){if (child + 1 < end && a[child + 1] < a[child])child++;if (a[parent] > a[child]){swap(&a[child],&a[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}
}
这里的逻辑就是,先使用假设法让child唯一
然后,比对a[child]和a[parent]的大小 满足条件则交换
大堆 a[child]>a[parent] 小堆 a[child]<a[parent]
如果不满足条件,直接结束.
测试堆
看图吧
这是小堆,所以是升序,也算是完成了