数据结构就是定义出某种结构:像数组结构、链表结构、树形结构等,实现数据结构就是我们主动去管理增删查改的实现函数
堆的概念理解
堆是把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中
大堆 树中所有父亲都大于等于孩子,小堆 树中所有父亲都小于等于孩子
下面在vs环境下编程实现动态堆的实现,我们分文件编写,在头文件 Heap.h 进行声明,在 Heap.c 当中进行具体的函数定义,在 test.c 当中的做具体的测试实现
先来了解一下头文件 Heap.h 当中接口
#pragma once//防止重复使用
#include<stdio.h>//所需要的头文件
#include<stdlib.h>//开辟内存所需头文件
#include<assert.h>//断言头文件
#include<stdbool.h>//bool头文件
//typedef 使用typedef想换类型时可以将int改成double、char等
typedef int HPDatatype;
typedef struct Heap//定义结构
{
HPDatatype* a;//指向动态开辟的数组
size_t size;//记录存储数据的个数
size_t capacity;//记录容量
}HP;
//函数的声明
void Adjustup(HPDatatype* a, size_t child);//向上调整算法
void Adjustdown(HPDatatype* a, size_t size, size_t root);//向下调整算法
void swap(HPDatatype* pa, HPDatatype* pb);//交换函数
void HeapInit(HP* heap);//初始化函数
void HeapDeatrory(HP* heap);//置空函数
void HeapPrint(HP* heap);//打印函数
void HeapPush(HP* heap, HPDatatype x);//堆的插入 二叉树
void HeapPop(HP* heap);//删除堆顶数据
bool HeapEmpty(HP* heap);//判断是否为空
size_t HeapSize(HP* heap);//堆的大小函数
HPDatatype HeapTop(HP* heap);//取堆顶数据
我们知道,函数的定义方法是非常重要的,也是我们需要深入理解的,下面我们详细学习在 Heap.c 当中具体的函数实现
初始化函数定义
void HeapInit(HP* heap)//初始化
{
assert(heap);
heap->a = NULL;//初始化为空
heap->size = heap->capacity = 0;
}
打印函数定义
void HeapPrint(HP* heap)//打印函数
{
//下标遍历 堆底层是数组
assert(heap);
for (size_t i = 0; i < heap->size; ++i)
{
printf("%d ", heap->a[i]);
}
printf("\n");
}
交换函数定义
void swap(HPDatatype* pa, HPDatatype* pb)//交换函数
{
//下面向上向下调整都需要 我们单独写一个函数
HPDatatype tmp = *pa;
*pa = *pb;
*pb = tmp;
}
向上调整算法
//向上调整算法
void Adjustup(HPDatatype*a ,size_t child)//传孩子
{
//算父亲
size_t parent = (child - 1) / 2;
while (child>0)//持续往上调整
{
//if (a[child] < a[parent])//小堆
if (a[child] > a[parent])//大堆
{
swap(&a[child], &a[parent]);//传地址交换
child = parent;//孩子更新 在算父亲
parent = (child - 1) / 2;
}
//中途就找到
else
{
break;//在中间就调整好的情况
}
}
}
向下调整算法
//向下调整算法 前提 左右都是小堆或大堆
void Adjustdown(HPDatatype* a, size_t size, size_t root)
{
//算孩子
size_t parent = root;
size_t child = parent * 2 + 1;//先找左孩子
while (child < size)//左孩子小于size 说明存在
{
//找出左右孩子中小的那个
//if (child+1<size&&a[child + 1] < a[child])//右孩子存在且右小于左 小堆
if (child + 1 < size && a[child + 1] > a[child])//右孩子存在且右大于左 大堆
{
++child;
}
//跟父亲比较 小的换就交换
//if (a[child] < a[parent])//小堆
if (a[child] > a[parent])//换这里换成大堆调整
{
swap(&a[child], &a[parent]);
//再从交换的位置继续往下调整
parent = child;
child = parent * 2 + 1;
}
//在中间就调整好的情况
else
{
break;
}
}
}
堆的插入函数定义
先插入一个数据到数组的尾上,再进行向上调整算法,直到满足堆
void HeapPush(HP* heap, HPDatatype x)//堆插入
{
assert(heap);
//插入数据有可能是堆 也可能不是
if (heap->size == heap->capacity)//空间扩容
{
size_t newcapacity = heap->capacity
== 0 ? 4 : heap->capacity * 2;
HPDatatype* tmp =
realloc(heap->a, sizeof(HPDatatype) * newcapacity);
if (tmp == NULL)
{
printf("realloc failed\n");
exit(-1);
}
//
heap->a = tmp;
heap->capacity = newcapacity;
}
//先插入到最后 size上
heap->a[heap->size] = x;
++heap->size;
//放进数据 要保持还是堆
//调用向上调整算法 跟祖先比较
Adjustup(heap->a, heap->size - 1);
}
删除堆顶数据函数
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法
void HeapPop(HP* heap)//堆的删除 堆顶
{
assert(heap);
//根位置和最后一个位交换
swap(&heap->a[0], &heap->a[heap->size - 1]);
heap->size--;//删除最后一个数据
//调用向下调整 高度次
Adjustdown(heap->a,heap->size,0);//从根开始调整
}
置空函数定义
置空函数一般会放在我们进行插入或删除的函数最后,释放我们在堆上申请的空间,将其还给操作系统,另外也会相应的进行检查越界等问题
void HeapDeatrory(HP* heap)//置空函数
{
//开辟空间需要置空,使用该函数
assert(heap);
free(heap->a);//置空后给空
heap->a = NULL;
heap->size = heap->capacity = 0;
}
判断是否为空函数定义
bool HeapEmpty(HP* heap)//判断是否为空
{
assert(heap);
return heap->size == 0;
}
堆的大小函数定义
size_t HeapSize(HP* heap)//堆的大小函数
{
assert(heap);
return heap->size;//返回我们的size
}
取堆顶数据函数
HPDatatype HeapTop(HP* heap)//取堆顶数据
{
assert(heap);
assert(heap->size > 0);
return heap->a[0];//返回数组中第一个
}
我们先用上面接口实现一个在 test.c 当中的测试案例void TestHeap
#include"Heap.h"
void TestHeap()
{
HP hp;
HeapInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 5);
HeapPush(&hp, 0);
HeapPush(&hp, 8);
HeapPush(&hp, 3);
HeapPush(&hp, 9);
//我们默认写的是大堆
HeapPrint(&hp);//打印大堆9 5 8 1 3 0
HeapPop(&hp);//删除堆顶数据
HeapPrint(&hp); //打印大堆8 5 0 1 3
HeapDeatrory(&hp);
}
int main()
{
TestHeap();
}
堆排序的理解
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆 升序:建大堆 降序:建小堆
2. 利用堆的删除思想来进行数据排序
我们实现一个升序 要建大堆 /空间复杂度O(1)
//堆排序 完全二叉树
void Heapsort(int* a, int n)
{
//在数组上建堆
//向上调整
for (size_t i = 1; i < n; ++i)
{
Adjustup(a, i);//调用向上调整算法
}
//向下调整建堆O(N)
//从倒数第一个非叶子节点 ,最后一个节点(n-1)的父亲 (n-1-1)/2开始
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
Adjustdown(a, n,i);//调用向下调整算法
}
//最大的数和最后一个交换
size_t end = n - 1;//
while (end>0)
{
swap(&a[0], &a[end]);
Adjustdown(a, end, 0);
//最后一个不看做堆里的 (堆的大小-1)在向下调整
--end;//大的数依次往后放
}
}
int main()
{
//给出一个数组,这个数组逻辑上可以看做一颗完全二叉树
//但是还不是一个堆,我们通过算法,可以把它构建成一个堆
int a[] = { 4,2,7,8,5,1,0,6 };
Heapsort(a,sizeof(a)/sizeof(int));
for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
{
printf("%d ", a[i]);//打印 0 1 2 4 5 6 7 8
}
}
在Java和C++的学习当中,前期学习数据结构当中的顺序表、链表、二叉树等便于我们后面更好的学习容器,后面会继续分算法、搜素二叉树的实现
希望这篇文章大家有所收获,我们下篇见