C语言实现堆
在介绍堆之前我们先需要介绍一个数据结构——二叉树的概念。
二叉树的概念:
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树 的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
特殊的二叉树:
1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
而我们的堆就是建立在完全二叉树的基础之上所提出的。
堆的概念:
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
堆的实现:
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
在具体实现堆的操作之前,我们先来讲述两个堆的调整算法:
1.向下调整算法:
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整 成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。所以,我们必须从整个二叉树的最后一个根节点开始调整。
算法的核心思想:
从根结点开始,然后一个一个的把结点插入堆中。当把一个新的结点插入堆中时,需要对结点进行调整,以保证插入结点后的堆依然是大根堆。如下图所示,是采用自顶向下的方法建立的大根堆。
图示如下:
具体实现如下:
//a为数组首地址,n为结点个数,root为当前需处理的根节点
void ShiftDown(HPDataType* a, int n, int root)
{
assert(a);
int parent = root;
int child = 2 * parent + 1;
//当前结点是否有孩子结点,有孩子进入循环,无孩子则无需调整
while (child < n)
{
//判断是否有右孩子,如果有,两者之间选较大值
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
//孩子是否大于父亲
if (a[child] > a[parent])
{
//交换
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
//更新下一次调整的位置
parent = child;
child = 2 * parent + 1;
}
else
{
//以parent为根的子树已经是一个大堆,结束调整
break;
}
}
}
向下调整算法的时间复杂度O(logN),多在创建堆时使用。
2.向上调整算法
向上调整算法相比较于向下调整算法要简洁许多,多用于在原有的堆的基础上进行调整的算法,其时间复杂度为O(N)。
算法的核心思想:
从第一个非叶子结点开始进行判断该子树是否满足堆的性质。如果满足就继续判断下一个点。否则,如果子树里面某个子结点有最大元素,则交换他们,并依次递归判断其子树是否仍满足堆性质。
图示:在原有大堆的基础上增加一个新结点,并将其调整成大堆。
使其一直沿着其祖先节点进行比较,如果大于其父节点,则交换,如果小于,则调整结束。
具体实现:
ShiftUp(HPDataType* a, int n, int child)
{
assert(a);//对参数的合法性进行检查
int parent = (child-1)/2;
while (child > 0)
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
总结:向下调整算法多用于创建堆的过程,而向上调整算法则多用于在原有堆的基础上进行操作。
堆的操作:
Heap.h文件
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
void HeapInit(Heap* hp, HPDataType* a, int n);
void HeapEmptyInit(Heap* hp);
void ShiftDown(HPDataType* a, int n, int root);//向下调整算法
void ShiftUp(HPDataType* a, int n, int child);//向上调整算法
void HeapDestory(Heap* hp);
void HeapPush(Heap* hp, HPDataType x);
void HeapPop(Heap* hp);
HPDataType HeapTop(Heap* hp);
int HeapSize(Heap* hp);
int HeapEmpty(Heap* hp);
void HeapSort(int* a, int n);// 堆排序
void TestHeap();
void HeapPrint(Heap* hp);
void TestHeapSort();
Heap.c文件
//创建一个大堆
void HeapInit(Heap* hp, HPDataType* a, int n)
{
int i = 0;
assert(hp && a);
hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
for (i = 0; i < n; i++)
{
hp->_a[i] = a[i];
}
//调整,从最后一颗子树开始调整
for (i = (n - 2) / 2; i >= 0; --i)
{
ShiftDown(hp->_a, n, i);
}
hp->_size = n;
hp->_capacity = n;
}
//初始化为空堆
void HeapEmptyInit(Heap* hp)
{
hp->_a = NULL;
hp->_capacity = 0;
hp->_size = 0;
}
//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//销毁堆
void HeapDestory(Heap* hp)
{
free(hp->_a);
hp->_a = NULL;
hp->_capacity = 0;
hp->_size = 0;
}
//插入一个新节点:先进性尾插,在使用向上调整算法进行调整
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//检查容量
if (hp->_size == hp->_capacity)
{
int newcapacity = hp->_capacity == 0 ? 10 : 2 * hp->_capacity;
hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
hp->_capacity = newcapacity;
}
//尾插
hp->_a[hp->_size] = x;
hp->_size++;
//向上调整
ShiftUp(hp->_a, hp->_size, hp->_size - 1);
}
//删除堆顶元素,最值。
void HeapPop(Heap* hp)
{
assert(hp);
//对堆有效性进行检查
if (HeapEmpty(hp) == 0)
{
//交换堆顶元素
Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
//尾删
hp->_size--;
//进行调整
ShiftDown(hp->_a, hp->_size, 0);
}
}
//返回堆顶元素
HPDataType HeapTop(Heap* hp)
{
assert(hp && hp->_size);
return hp->_a[0];
}
//返回堆的大小
int HeapSize(Heap* hp)
{
assert(hp && hp->_size);
return hp->_size;
}
//判断堆是否为空
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->_size == 0 ? 1 : 0;
}
// 堆排序 :O(N*log(N)+N(建堆))--->O(N)
void HeapSort(int* a, int n)
{
//建堆
int i = 0;
int end = n - 1;
for (i = (n - 2) / 2; i >= 0; --i)
{
ShiftDown(a, n, i);
}
//堆排序
//交换
while (end > 0)
{
Swap(&a[0], &a[end]);
ShiftDown(a, end, 0);
end--;
}
}
//测试
void TestHeap()
{
Heeap hp;
HeapEmptyInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 2);
HeapPush(&hp, 3);
HeapPush(&hp, 4);
HeapPush(&hp, 5);
HeapPush(&hp, 6);
HeapPush(&hp, 7);
HeapPop(&hp);
HeapPrint(&hp);
}
//打印堆
void HeapPrint(Heap* hp)
{
assert(hp);
for (int i = 0; i < hp->_size; ++i)
{
printf("%d ", hp->_a[i]);
}
printf("\n");
}
//测试堆排序
void TestHeapSort()
{
int i = 0;
int a[] = { 1,5,3,8,7,6 };
HeapSort(a, sizeof(a) / sizeof(a[0]));
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
TestHeap();
TestHeapSort();
return 0;
}