Hello,我们又见面了,今天讲的是堆,是数据结构里的堆,可不是我们C语言的堆区那个,今天这篇文章我们写一个数组堆,然后实现堆排序的功能,废话不多说,开始我们今天的学习吧!!
堆的概念与性质
堆是计算机中的一类特殊的数据结构的统称。堆通常是可以被看做一颗完全二叉树的数组对象.
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
堆又分为大小堆,大堆的特点就是父亲是最大的,后面我会补文章来讲解数的一些概念,小堆就是父亲是最小的
举列子给大家看可能更好的理解。
上面的是小堆,因为我们的父亲最小,下面的是大堆,父亲是最大的数
那我们有没有想过堆的作用是什么
第一个作用就是解决堆排序的问题,我们可以用它来排序数组,因为数组是我们的物理结构,但是不是任何数组都可以用来排序的,我们还得满足它的逻辑结构,那就是左右必须得是子数,满足大小堆的结构.
那我们先来实现一个数组堆,和之前的顺序表和链表一样,我们有我们的结构。
c
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* arry;
int size;
int capacity;
}Heap;
这么一看我们定义的结构体就是我们顺序表定义过的结构体!!!
那我们的接口函数实现
c
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* arry;
int size;
int capacity;
}Heap;
void HeapInit(Heap* php);
void HeapDestory(Heap* php);
void HeapPrint(Heap* php);
bool HeapEmpty(Heap* php);
void HeapPush(Heap* php, HeapDataType x);
void HeapPop(Heap* php);
HeapDataType HeapTop(Heap* php);
int HeapSize(Heap* php);
那我们一个一个来实现吧,首先就是我们的初始化堆,这个很简单,和顺序表一摸一样的操作就可以实现,我们来看一下吧
c
void HeapInit(Heap* php)
{
assert(php);
php->arry = NULL;
php->capacity = php->size = 0;
}
这个应该很简单来了,对于我们来说,不懂可以看前面的文章去看看
有了初始化就有销毁,销毁的时我们动态开辟的空间。我们也来实现一下吧
cpp
void HeapDestory(Heap* php)
{
assert(php);
free(php->arry);
}
现在我们继续实现堆打印的函数
c
void HeapPrint(Heap* php)
{
assert(php);
int i = 0;
for (i = 0; i < php->size; i++)
{
printf("%d ", php->arry[i]);
}
printf("\n");
}
遍历一遍数组其实就能实现,因为我们的物理结构就是数组,所以这里很简单,接下来就是怎么放入数组,这个有点难里面有好几个接口函数需要我们手动实现,这里我们拿小堆升序来讲
首先我们插入,应该是从他的尾巴插入,就是数组最后一个,插入的话,我们是不是需要实现一个接口函数检查受否要扩容,检查这个扩容我们已经实现过好几次了,这里我就不讲解了,然后我们需要把要插入的数放进去,size++就行了,但是插入一个数之后,二叉数堆就不是我们之前的小堆,需要进行调整,这里我们就用一个巧妙的方法,向上调整,调整我们的数到合适位置,调整比较的是它的祖先,而不是随意的进行比较,因为我们的堆是小堆,所以这个数如果比他的祖先小,就需要进行调整,最坏的就是这个数是最小的,这时候我们要给定一个条件来结束调整。那我们先来看一下完整的代码吧
c
void swap(HeapDataType* p1, HeapDataType* p2)
{
HeapDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void Adjustup(HeapDataType* arry, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arry[child] < arry[parent])
{
swap(&arry[child], &arry[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* php, HeapDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HeapDataType* tmp = (HeapDataType*)realloc(php->arry, sizeof(HeapDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->arry = tmp;
php->capacity = newcapacity;
}
php->arry[php->size] = x;
php->size++;
Adjustup(php->arry, php->size - 1);
}
这就是我们的调整,我们走读代码来到调整的部分,我们比较的是父亲和孩子,如果孩子比父亲小就进行交换,反之退出循环,还可以用递归的方法来写,但是一般情况下堆不会使用。
有了数据的插入就有数据的删除,来看看删除的向下调整,也是同样的思路。
c
void AdjustDown(HeapDataType* arry, int size, int parent)
{
int child = 2 * parent + 1;
if (child+1<size && arry[child + 1] < arry[child])
{
child++;
}
while (child < size)
{
if (arry[parent] > arry[child])
{
swap(&arry[parent], &arry[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* php)
{
assert(php);
assert(php->size > 0);
swap(&(php->arry[0]), &(php->arry[php->size - 1]));
php->size--;
AdjustDown(php->arry, php->size, 0);
}
这个内容主要就是讲pop和push这两个,其他结构函数这里小编一并实现了,不是特别难,我们来看看吧
c
HeapDataType HeapTop(Heap* php)
{
assert(php);
return php->arry[0];
}
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
这两个一个是取出堆顶的数,就是我们的根,第二个是个数。
还有与一个判断堆是否是空的函数,让我们一起来看看吧
c
bool HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
完整代码
c
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* arry;
int size;
int capacity;
}Heap;
void HeapInit(Heap* php);
void HeapDestory(Heap* php);
void HeapPrint(Heap* php);
bool HeapEmpty(Heap* php);
void HeapPush(Heap* php, HeapDataType x);
void HeapPop(Heap* php);
HeapDataType HeapTop(Heap* php);
int HeapSize(Heap* php);
c
#include"Heap.h"
void HeapInit(Heap* php)
{
assert(php);
php->arry = NULL;
php->capacity = php->size = 0;
}
void HeapDestory(Heap* php)
{
assert(php);
free(php->arry);
}
void HeapPrint(Heap* php)
{
assert(php);
int i = 0;
for (i = 0; i < php->size; i++)
{
printf("%d ", php->arry[i]);
}
printf("\n");
}
bool HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0;
}
void swap(HeapDataType* p1, HeapDataType* p2)
{
HeapDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void Adjustup(HeapDataType* arry, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arry[child] < arry[parent])
{
swap(&arry[child], &arry[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* php, HeapDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HeapDataType* tmp = (HeapDataType*)realloc(php->arry, sizeof(HeapDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
php->arry = tmp;
php->capacity = newcapacity;
}
php->arry[php->size] = x;
php->size++;
Adjustup(php->arry, php->size - 1);
}
void AdjustDown(HeapDataType* arry, int size, int parent)
{
int child = 2 * parent + 1;
if (child+1<size && arry[child + 1] < arry[child])
{
child++;
}
while (child < size)
{
if (arry[parent] > arry[child])
{
swap(&arry[parent], &arry[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* php)
{
assert(php);
assert(php->size > 0);
swap(&(php->arry[0]), &(php->arry[php->size - 1]));
php->size--;
AdjustDown(php->arry, php->size, 0);
}
HeapDataType HeapTop(Heap* php)
{
assert(php);
return php->arry[0];
}
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
懒得去测试了,大家就这样看看吧,那今天的分享就到这里,我们后面讲讲怎么用堆来实现堆排序吧