最近找实习,复习下数据结构方面的内容。
完全二叉树有两种形态,一种是:二叉树的所有子树要么没有孩子,要么一定有左孩子。另一种是:二叉树要么没有子树,要么一定左右子树都有。
堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左孩子和右孩子节点的值。
最大堆和最小堆是二叉堆的两种形式。
最大堆:根结点的键值是所有堆结点键值中最大者。
最小堆:根结点的键值是所有堆结点键值中最小者。
在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高进先出 (largest-in,first-out)的行为特征。优先队列可以用堆来实现。
下面我们用数组来实现一个最小堆。
代码如下:
MinHeap.h
#ifndef DataStructures_MinHeap_h#define DataStructures_MinHeap_hstruct MinHeap;
typedef struct MinHeap * MinPriorityQueue;
typedef int ElementType;
// 初始化堆MinPriorityQueue initialize(int maxElements);
// 销毁堆void destroy(MinPriorityQueue pqueue);
// 清空堆中的元素void makeEmpty(MinPriorityQueue pqueue);
// 插入操作void insert(ElementType x, MinPriorityQueue pqueue);
// 删除最小者操作,返回被删除的堆顶元素ElementType deleteMin(MinPriorityQueue pqueue);
// 查找最小者(堆顶)ElementType findMin(MinPriorityQueue pqueue);
// 判断堆是否为空int isEmpty(MinPriorityQueue pqueue);
// 判断堆是否满int isFull(MinPriorityQueue pqueue);
// 通过一个数组来建堆,相当于将用数组实现的无序树转换为堆序树MinPriorityQueue buildHeap_insert(int *arr, int n);
MinPriorityQueue buildHeap_percolate(int *arr, int n);
// 打印堆void printMinPriorityQueue(MinPriorityQueue pqueue);
#endif
MinHeap.c
#include #include #include "MinHeap.h"/* 标记节点,类似于链表中的表头节点 * 该值必须小于所有最小堆中的元素,设其值为-1 */#define SentinelElement -1/* * 使用数组实现堆 * * capacity 数组的最大容量 * size 数组的长度 * elements 堆中的元素存放的数组 */struct MinHeap{
int capacity;
int size;
ElementType *elements;
// 堆的元素个数为size,实际上用来存储的数组的长度为size + 1,还包括一个sentinel元素}
;
voidPQueueNULLWarning(){
printf("Warning: Minimum Priority Queue is NULL");
}
voidoutOfSpaceFatalError(){
printf("Fatal Error: Out of space");
abort();
}
MinPriorityQueueinitialize(int maxElements){
MinPriorityQueue pqueue;
if (maxElements <= 0) {
printf("Fail to initialize: maxElements <= 0");
return NULL;
}
pqueue = malloc(sizeof(struct MinHeap));
if (pqueue == NULL) outOfSpaceFatalError();
// 数组的第0个元素是个sentinel标记节点,计入数组容量中,但不计入capcaity或size中 pqueue->size = 0;
pqueue->capacity = maxElements;
pqueue->elements = malloc(sizeof(ElementType) * (pqueue->capacity + 1));
if (pqueue->elements == NULL) outOfSpaceFatalError();
else pqueue->elements[0] = SentinelElement;
return pqueue;
}
voiddestroy(MinPriorityQueue pqueue){
if (pqueue != NULL) {
// 在GNU99标准中,free(NULL)什么都不做直接返回,所以不用判断pqueue->elements是否为NULL free(pqueue->elements);
free(pqueue);
}
}
voidmakeEmpty(MinPriorityQueue pqueue){
if (pqueue != NULL) pqueue->size = 0;
else PQueueNULLWarning();
}
/* * 插入时,堆中的元素执行下滤操作 * 删除时,堆中的元素执行上滤操作 *//* * 插入的时间复杂度为O(log N),N为最小堆中的元素个数 * 实际上,其平均执行时间为O(1) */voidinsert(ElementType x, MinPriorityQueue pqueue){
if (pqueue == NULL) PQueueNULLWarning();
if (isFull(pqueue)) {
printf("Fail to insert: Priority Queue is Full");
return;
}
else {
int i;
// sentinel element在这里作为elements[0]被比较,是循环的终止条件 for (i = ++pqueue->size;
x < pqueue->elements[i / 2];
i /= 2) pqueue->elements[i] = pqueue->elements[i / 2];
// 下滤操作 pqueue->elements[i] = x;
}
}
/* * 删除操作的平均时间为O(log N) */ElementTypedeleteMin(MinPriorityQueue pqueue){
if (pqueue == NULL) {
PQueueNULLWarning();
return SentinelElement;
}
if (isEmpty(pqueue)) {
printf("Fail to delete: Priority Queue is Empty");
return SentinelElement;
}
int i, child;
ElementType minElement, lastElement;
// 注意对某个节点进行上滤操作时,要判断该节点是有两个儿子还是一个儿子 minElement = pqueue->elements[1];
lastElement = pqueue->elements[pqueue->size--];
for (i = 1;
i * 2 <= pqueue->size;
i = child) {
child = i * 2;
// 节点i只有一个儿子时必有i * 2 = pqueue->size if (child < pqueue->size && pqueue->elements[child] > pqueue->elements[child + 1]) child++;
if (lastElement < pqueue->elements[child]) break;
else pqueue->elements[i] = pqueue->elements[child];
// 上滤操作 }
pqueue->elements[i] = lastElement;
return minElement;
// 返回被删除的元素}
/* * 执行时间:O(1) */ElementTypefindMin(MinPriorityQueue pqueue){
if (pqueue == NULL) {
PQueueNULLWarning();
return SentinelElement;
}
else return pqueue->elements[1];
}
intisEmpty(MinPriorityQueue pqueue){
if (pqueue == NULL) {
PQueueNULLWarning();
return -1;
}
else return (pqueue->size == 0);
}
intisFull(MinPriorityQueue pqueue){
if (pqueue == NULL) {
PQueueNULLWarning();
return -1;
}
else return (pqueue->size == pqueue->capacity);
}
voidpercolateDown(int *arr, int len, int i){
int n = len - 1;
int tmp;
if (i * 2 == n && arr[i] > arr[n]) // 只有左儿子的节点,并且左儿子比本节点的值要小,交换 {
tmp = arr[i];
arr[i] = arr[n];
arr[n] = tmp;
}
else // 有两个儿子的节点 {
if (arr[i * 2] > arr[i * 2 + 1]) // 右儿子较小 {
if (arr[i] > arr[i * 2 + 1]) // 如果本节点比右儿子大,交换 {
tmp = arr[i];
arr[i] = arr[i * 2 + 1];
arr[i * 2 + 1] = tmp;
}
}
else // 左儿子较小 {
if (arr[i] > arr[i * 2]) // 如果本节点比左儿子大,交换 {
tmp = arr[i];
arr[i] = arr[i * 2];
arr[i * 2] = tmp;
}
}
}
}
MinPriorityQueuebuildHeap_percolate(int *arr, int n){
if (arr == NULL) {
printf("Error: Array is NULL");
return NULL;
}
MinPriorityQueue pqueue;
pqueue = malloc(sizeof(struct MinHeap));
if (pqueue == NULL) outOfSpaceFatalError();
ElementType *elements = malloc(sizeof(ElementType) * (n + 1));
if (elements == NULL) outOfSpaceFatalError();
int i;
for (i = 1;
i <= n;
i++) elements[i] = arr[i - 1];
elements[0] = SentinelElement;
for (i = n / 2;
i > 0;
i--) percolateDown(elements, n + 1, i);
pqueue->elements = elements;
pqueue->size = n;
pqueue->capacity = n * 2;
return pqueue;
}
/* * 通过n次插入元素建立堆,由于每次插入的平均执行时间为O(1),所以建堆平均时间为O(N) */MinPriorityQueuebuildHeap_insert(int *arr, int n){
MinPriorityQueue pqueue;
if (arr == NULL) {
printf("Array is NULL, fail to build heap");
return NULL;
}
pqueue = initialize(n * 2);
for (int i = 0;
i < n;
i++) insert(arr[i], pqueue);
return pqueue;
}
voidprintMinPriorityQueue(MinPriorityQueue pqueue){
if (pqueue == NULL) {
PQueueNULLWarning();
return;
}
if (pqueue->elements == NULL) {
printf("Fail to print: Elements of priority queue is NULL");
return;
}
if (isEmpty(pqueue)) {
printf("Empty Prioirty Queuen");
return;
}
printf("Priority Queuen");
for (int i = 1;
i <= pqueue->size;
i++) printf("Element[%d] = %dn", i, pqueue->elements[i]);
printf("n");
}
建堆的测试代码:
#include #include #include "MinHeap.h"int main(int argc, const char * argv[]){
int a[5] = {
5, 4, 3, 2, 1}
;
MinPriorityQueue pqueue_ins = buildHeap_insert(a, 5);
MinPriorityQueue pqueue_per = buildHeap_percolate(a, 5);
printMinPriorityQueue(pqueue_ins);
printMinPriorityQueue(pqueue_per);
return 0;
}
分别使用插入和下滤两种方式建堆,所以建立的结果是不同的,输出如下:
Priority QueueElement[1] = 1Element[2] = 2Element[3] = 4Element[4] = 5Element[5] = 3Priority QueueElement[1] = 1Element[2] = 5Element[3] = 3Element[4] = 2Element[5] = 4
最大堆实现类似。