二叉樹的順序結構:
普通的二叉樹是不適合用數組來存儲的,因爲可能會存在大量的空間浪費。而完全二叉樹更適合使用順序結構存儲。現實中我們通常把堆(一種二叉樹)使用順序結構的數組來存儲。需要注意的是這裏的堆和操作系統虛擬進程地址空間中的堆是兩回事,一個是數據結構,一個是操作系統中管理內存的一塊區域分段。
堆的概念
如果有一個關鍵碼的集合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--;
}
}