堆的基本操作
程序员文章站
2022-03-31 20:01:54
...
堆的性质
首先, 这个堆和堆栈那个堆没有一点关系
这个堆就是一棵完全二叉树
分为大堆和小堆
满足以下性质
如果是大堆, 那么任意根节点的孩子节点都比它小
如果是小堆, 那么任意根节点的孩子节点都比它大
堆的基本操作
- 初始化
- 插入
- 删除
- 取堆顶元素
- 销毁
- 堆排序
我们每次插入完或者删除完以后, 这个堆仍然要满足堆的性质, 所以就需要在插入删除以后再做一些操作, 使元素上浮或者下沉, 使其仍然满足堆的性质
由于堆是一棵完全二叉树, 所以它的父子节点有这些关系
定义:
father : 父节点下标
lchild: 左孩子节点的下标
rchild: 右孩子节点的下标
则有:
lchild = father * 2 + 1
rchild = father * 2 + 2
father = (child - 1) / 2
(其中 child 为左右任意孩子的下标, 因为 2/2 = 1, 3/2 = 1)
因为有大小堆的区分, 所以我们需要用回调函数 compare 来制定比较规则, 以此决定构建的是大堆还是小堆
实现代码
/*================================================================
# File Name: heap.h
# Author: rjm
# mail: aaa@qq.com
# Created Time: 2018年05月12日 星期六 14时43分40秒
================================================================*/
#pragma once
// 1. 堆是一个完全二叉树
// 2. 堆有两种, 一种叫小堆(小根堆, 最小堆),
// 一种叫大堆(大根堆, 最大堆).
// 3. 以小堆为例, 这个树的根节点是这个树中的最小的元素
// 对于任意一个子树来说, 子树的根节点, 小于左右孩子节点的值.
// 4. 以大堆为例, 这个树的根节点是这个树中的最大的元素
// 对于任意一个子树来说, 子树的根节点, 大于左右孩子节点的值.
#include <stdio.h>
#define TEST_HEAD printf("\n===============%s=================\n", __FUNCTION__)
#define HeapMaxSize 1000
typedef int HeapType;
typedef int (*Compare)(HeapType a, HeapType b);
typedef struct Heap
{
HeapType data[HeapMaxSize];
size_t size;
Compare cmp;
} Heap;
// 初始化堆
void HeapInit(Heap *heap, Compare cmp);
// 插入元素
void HeapInsert(Heap *heap, HeapType value, Compare cmp);
// 取堆顶元素
int HeapTop(Heap *heap, HeapType *value);
// 删除堆顶元素
void HeapErase(Heap *heap, Compare cmp);
// 清空堆
int HeapEmpty(Heap *heap);
// 求堆的大小
size_t HeapSize(Heap *heap);
// 销毁堆
void HeapDestroy(Heap *heap);
// 向下调整
void AdjustDown();
// 打印堆
void printHeap();
// 在我们不想开辟额外的空间, 或者消耗额外的时间的前提下,
// 如果我们想进行从小到大排序, 就需要一个大堆
// 如果我们想进行从大到小排序, 就需要一个小堆
void HeapSort(HeapType array[], size_t size, Compare cmp);
/*================================================================
# File Name: heap.c
# Author: rjm
# mail: aaa@qq.com
# Created Time: 2018年05月11日 星期五 14时09分13秒
================================================================*/
#include <stdio.h>
#include <string.h>
#include "heap.h"
//堆: 是一棵完全二叉树
//最大堆: 每个根节点的值都大于左右孩子节点的值
//最小堆: 每个根节点的值都小于左右孩子节点的值
int cmp_max(HeapType a, HeapType b)
{
return a > b ? 1 : 0;
}
int cmp_min(HeapType a, HeapType b)
{
return a < b ? 1 : 0;
}
//交换数组元素的函数
void Swap(int a, int b, Heap* heap)
{
HeapType tmp = heap->data[a];
heap->data[a] = heap->data[b];
heap->data[b] = tmp;
}
//初始化堆
void HeapInit(Heap* heap, Compare cmp)
{
heap->size = 0;
cmp = NULL;
}
//插入元素
void HeapInsert(Heap* heap, HeapType value, Compare cmp)
{
if(heap == NULL)
{
return ;
}
if(heap->size == HeapMaxSize)
{
//堆已经满了
return ;
}
//否则, 先将其插入堆的最后, 下标为size
//再将其上浮, 直到满足堆的规则
heap->data[heap->size] = value;
int cur = heap->size;
int father = (cur-1)/2;
while(cur > 0 && cmp( heap->data[cur], heap->data[father] ) == 1)
{
Swap(cur, father, heap);
cur = father;
father = (cur-1)/2;
}
++heap->size;
}
// 取堆顶元素
int HeapTop(Heap *heap, HeapType *value)
{
if(heap == NULL)
{
//堆为空
return 0;
}
*value = heap->data[0];
return 1;
}
// 删除堆顶元素
void HeapErase(Heap *heap, Compare cmp)
{
if(heap == NULL)
{
return ;
}
if(heap->size == 0)
{
//空堆
return ;
}
//交换堆顶元素和最后一个元素
Swap(0, (heap->size)-1, heap);
//size--, 删除最后一个元素
--heap->size;
//然后再进行调整, 使其满足堆的性质
AdjustDown(heap, 0, cmp);
}
//清空堆
int HeapEmpty(Heap *heap)
{
if(heap == NULL)
//空堆
return 0;
heap->size = 0;
return 1;
}
//求堆的大小
size_t HeapSize(Heap *heap)
{
if(heap == NULL)
//空堆
return 0;
return heap->size;
}
//销毁堆
void HeapDestroy(Heap *heap)
{
if(heap == NULL)
return ;
heap->size = 0;
}
//将一个数组变为一个堆
void Array_to_Heap(Heap* heap, HeapType* arr, int arr_size, Compare cmp)
{
if(arr == NULL)
return ;
for(int i=0; i<arr_size; i++)
{
HeapInsert(heap, arr[i], cmp);
}
memcpy(arr, heap->data, sizeof(HeapType)*arr_size);
}
// 堆排序
// 在我们不想开辟额外的空间, 或者消耗额外的时间的前提下,
// 如果我们想进行从小到大排序, 就需要一个大堆
// 如果我们想进行从大到小排序, 就需要一个小堆
void HeapSort(HeapType array[], size_t size, Compare cmp)
{
if(array == NULL)
return ;
Heap heap;
HeapInit(&heap, cmp);
Array_to_Heap(&heap, array, size, cmp);
printHeap(&heap, "堆排序");
HeapType value;
for(int i=0; i<size; i++)
{
HeapTop(&heap, &value);
HeapErase(&heap, cmp);
array[i] = value;
}
}
//向下调整
void AdjustDown(Heap* heap, int index, Compare cmp)
{
if(index < 0)
return ;
int tmp = 0;//用来临时保存最小的值的下标
int IsNeedAdjust_flag = 0;//判断是否需要调整的标志
//当前节点有孩子 并且 需要调整时, 进入循环
while(index*2+1 < heap->size && IsNeedAdjust_flag == 0)
{
//如果右子树存在, 先比较左右子树的大小
if(index*2+2 < heap->size)
{
if(cmp(heap->data[index*2+1], heap->data[index*2+2]) == 1)
{
tmp = index*2+1;
}
else
{
tmp = index*2+2;
}
if(cmp(heap->data[index], heap->data[tmp]) == 0)
{
Swap(index, tmp, heap);
index = tmp;
}
else
IsNeedAdjust_flag = 1;
}
//如果右子树不存在, 就和左子树比较
else
{
if(cmp(heap->data[index], heap->data[index*2+1]) == 0)
{
Swap(index, index*2+1, heap);
index = index*2+1;
}
else
IsNeedAdjust_flag = 1;
}
}
}
//打印堆
void printHeap(Heap* heap, char* msg)
{
printf("\n=====%s=====\n", msg);
if(heap == NULL)
return ;
for(int i=0; i<heap->size; i++)
{
printf("[%2d|%2d] ", i, heap->data[i]);
}
printf("\n");
}
/*********************************************************
测试代码
*********************************************************/
//测试堆的创建
void TestHeapInsert()
{
TEST_HEAD;
Heap heap;
HeapInit(&heap, cmp_min);
HeapInsert(&heap, 1, cmp_min);
HeapInsert(&heap, 3, cmp_min);
HeapInsert(&heap, 0, cmp_min);
HeapInsert(&heap, 5, cmp_min);
HeapInsert(&heap, 4, cmp_min);
HeapInsert(&heap, 7, cmp_min);
HeapInsert(&heap, 9, cmp_min);
HeapInsert(&heap, 8, cmp_min);
HeapInsert(&heap, 6, cmp_min);
HeapInsert(&heap, 2, cmp_min);
printHeap(&heap, "往堆中插入4个元素");
}
//测试删除堆顶元素
void TestHeapDeleteTop()
{
TEST_HEAD;
Heap heap;
HeapInit(&heap, cmp_min);
HeapInsert(&heap, 1, cmp_min);
HeapInsert(&heap, 2, cmp_min);
HeapInsert(&heap, 3, cmp_min);
HeapInsert(&heap, 4, cmp_min);
HeapErase(&heap, cmp_min);
printHeap(&heap, "删除一次堆顶元素");
HeapErase(&heap, cmp_min);
HeapErase(&heap, cmp_min);
printHeap(&heap, "再删除两次堆顶元素");
HeapErase(&heap, cmp_min);
printHeap(&heap, "再删除一次堆顶元素");
HeapErase(&heap, cmp_min);
printHeap(&heap, "对空堆删除");
}
//测试将一个数组变为一个堆
void TestArrToHeap()
{
TEST_HEAD;
HeapType arr[] = {9, 1, 8, 2, 7, 3, 6, 4, 0, 5};
int size = sizeof(arr)/sizeof(arr[0]);
printf("size = %d\n", size);
Heap heap;
HeapInit(&heap, cmp_min);
Array_to_Heap(&heap, arr, size, cmp_max);
for(int i=0; i<size; i++)
{
printf("[%2d|%2d] ", i, arr[i]);
}
}
//测试获取堆顶元素
void TestHeapTop()
{
TEST_HEAD;
Heap heap;
HeapInit(&heap, cmp_min);
HeapInsert(&heap, 1, cmp_min);
HeapInsert(&heap, 2, cmp_min);
HeapInsert(&heap, 3, cmp_min);
HeapInsert(&heap, 4, cmp_min);
HeapType value;
HeapTop(&heap, &value);
printf("expect 1, actual %d\n", value);
}
//测试堆排序
void TestHeapSort_min()
{
TEST_HEAD;
HeapType arr[] = {1, 3, 0, 5, 4, 7, 9, 8, 6, 2};
//HeapType arr[] = {9, 1, 8, 2, 7, 3, 6, 4, 0, 5};
int size = sizeof(arr)/sizeof(arr[0]);
HeapSort(arr, size, cmp_min);
for(int i=0; i<size; i++)
{
printf("[%d]", arr[i]);
}
}
void TestHeapSort_max()
{
TEST_HEAD;
HeapType arr[] = {1, 3, 0, 5, 4, 7, 9, 8, 6, 2};
//HeapType arr[] = {9, 1, 8, 2, 7, 3, 6, 4, 0, 5};
int size = sizeof(arr)/sizeof(arr[0]);
HeapSort(arr, size, cmp_max);
for(int i=0; i<size; i++)
{
printf("[%d]", arr[i]);
}
}
int main()
{
TestHeapInsert();
TestHeapDeleteTop();
TestHeapTop();
TestArrToHeap();
TestHeapSort_min();
TestHeapSort_max();
printf("\n");
printf("\n");
printf("\n");
return 0;
}