二叉树的顺序结构:
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的特性:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树;
每个子树都是一个堆;
接口的实现:
创建堆:从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆
向下调整AdjustDown算法:根据父节点总是大于或者小于子节点的值进行调整
向上调整AdjustUp算法:根据左右子树都是一个堆进行调整
#pragma once
/*
** 堆中某个节点的值总是不大于或不小于其父节点的值
** 堆总是一个完全二叉树
*/
typedef int DataType;
typedef struct Heap {
DataType* array;
int capacity; // 能够容纳的节点个数
int size; // 实际节点个数
} Heap;
typedef int(*CMP_)(DataType, DataType); // 声明函数指针
// 比较函数
int Less(DataType x, DataType y);
int Greater(DataType x, DataType y);
void Swap(DataType* x, DataType* y);
void CheckCapacity(Heap* Heap); // 扩容
void AdjustDown(Heap* Heap, int size, int parent, CMP_ CMP);
void AdjustUp(Heap* Heap, int size, int parent, CMP_ CMP);
void CreateHeap(Heap* Heap, DataType* arr, int size, CMP_ CMP);
void Insert(Heap* Heap, DataType value); // 插入
int Size(Heap* Heap); // 堆的节点个数
int Empty(Heap* Heap); // 堆是否为空
DataType Top(Heap* Heap); // 返回堆顶
void Erase(Heap* Heap); // 删除堆顶
void Destroy(Heap* Heap);
#include "heap.h"
#include <assert.h>
#include <malloc.h>
void Swap(DataType* x, DataType* y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
void CheckCapacity(Heap* Heap) {
assert(Heap != NULL);
if (Heap->size == Heap->capacity) {
// 1.申请一段更大的内存
int NewCapacity = Heap->capacity * 2;
DataType* NewArr = (DataType*)malloc(sizeof(DataType) * NewCapacity);
if (NewArr == NULL) {
assert(NewArr);
return;
}
// 2.赋值
for (int i = Heap->size - 1; i >= 0; --i) {
NewArr[i] = Heap->array[i];
}
// 3.释放旧空间
free(Heap->array);
Heap->array = NewArr;
Heap->capacity = NewCapacity;
}
}
void CreateHeap(Heap* Heap, DataType* arr, int size, CMP_ CMP) {
assert(Heap != NULL);
// 1.为存放二叉堆的数组动态申请一段空间
Heap->array = (DataType*)malloc(sizeof(DataType) * size);
if (Heap->array == NULL) {
assert(Heap->array);
return;
}
// 2.将二叉树层序遍历后得到的数组,赋给动态申请的空间
for (int i = 0; i < size; ++i) {
Heap->array[i] = arr[i];
}
Heap->capacity = size;
Heap->size = size;
// 父节点与孩子节点下标的关系
// 父节点:n
// 左孩子节点:n*2+1 右孩子节点:n*2+2
// 孩子节点与父节点的关系: (孩子节点 - 1)/ 2 == 父亲节点
int last = ((Heap->size - 1 - 1) >> 1); // 获取最后一个非叶子节点的下标
// (小)堆的性质:堆顶元素小于所有的元素
while ( last >= 0 ) {
// 在所有的子树都具备堆的性质,即根节点大于左右子树时,退出循环
AdjustDown(Heap, size, last, CMP);
last--;
}
}
void AdjustDown(Heap* Heap, int size, int parent, CMP_ CMP) {
assert(Heap != NULL);
int child = parent * 2 + 1; // 默认孩子节点为左孩子
while (child < size) {
if (child + 1 < size
&& CMP(Heap->array[child + 1], Heap->array[child])) {
// 更改孩子节点里面较小的下标
child += 1;
}
if ( !CMP(Heap->array[parent], Heap->array[child]) ) {
// 如果父节点大于孩子节点(不符合小堆),交换父子节点
Swap(&Heap->array[child], &Heap->array[parent]);
// 更新父子节点
parent = child;
child = parent * 2 + 1;
}
else {
// 符合小堆的性质退出循环
return;
}
}
}
void AdjustUp(Heap* Heap, int size, int child, CMP_ CMP) {
assert(Heap != NULL);
int parent = ((child - 1) >> 1);
while (parent >= 0) {
AdjustDown(Heap, size, parent, CMP);
parent = ((parent - 1) >> 1);
}
}
void Insert(Heap* Heap, DataType value) {
assert(Heap != NULL);
CheckCapacity(Heap);
Heap->array[Heap->size] = value;
Heap->size++;
AdjustUp(Heap, Heap->size, Heap->size - 1, Less);
} // 插入
int Size(Heap* Heap) {
assert(Heap != NULL);
return Heap->size;
} // 堆的节点个数
int Empty(Heap* Heap) {
assert(Heap != NULL);
return (Heap->size == 0);
} // 堆是否为空
DataType Top(Heap* Heap) {
assert(Heap != NULL);
return Heap->array[0];
} // 返回堆顶
void Erase(Heap* Heap) {
assert(Heap != NULL);
Swap(&Heap->array[0], &Heap->array[Heap->size - 1]);
Heap->size--;
AdjustDown(Heap, Heap->size, 0, Less); // 交换以后,调整堆顶元素
} // 删除堆顶
void Destroy(Heap* Heap) {
assert(Heap != NULL);
free(Heap->array);
Heap->size = 0;
Heap->capacity = 0;
}
#include "heap.h"
#include <stdio.h>
#include <stdlib.h>
int Less(DataType x, DataType y) {
if (x < y) {
return 1;
}
return 0;
}
int Greater(DataType x, DataType y) {
if (x > y) {
return 1;
}
return 0;
}
void Print(Heap* Heap) {
for (int i = 0; i < Heap->size; i++) {
printf("%d ", Heap->array[i]);
}
printf("\n");
}
int main() {
Heap heap;
int arr[] = { 27,65,19,37,28,34,15,49,25,18 };
int len = sizeof(arr) / sizeof(arr[0]);
CreateHeap(&heap, arr, len, Less);
printf("top = %d\n", Top(&heap));
printf("size = %d\n", Size(&heap));
Print(&heap);
Insert(&heap, 5);
printf("top = %d\n", Top(&heap));
printf("size = %d\n", Size(&heap));
Print(&heap);
Erase(&heap);Erase(&heap);
printf("top = %d\n", Top(&heap));
printf("size = %d\n", Size(&heap));
Print(&heap);
Destroy(&heap);
system("pause");
return 0;
}
堆的应用---堆排序
#include "Head.h"
// 堆排序(特殊的选择排序)
// O(n^2) 不稳定
void Adjust(int arr[], int size, int parent) {
int child = parent * 2 + 1;
while (child < size) {
if (child + 1 < size && arr[child + 1] < arr[child]) {
child += 1;
}
if (arr[child] < arr[parent]) {
swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else {
return;
}
}
}
void HeapSort(int arr[], int size) {
// 1.创建堆
int last = ((size - 1 - 1) >> 1);
while (last >= 0) {
Adjust(arr, size, last);
last--;
}
// 2.排序
int end = size - 1;
while (end >= 0) {
swap(&arr[0], &arr[end]);
Adjust(arr, end, 0);
end--;
}
}