目录
1.1二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.2堆的概念及结构
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
1.3堆的实现
1.3.1堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。
向下调整算法有一个前提条件:左右子树必须是一个堆,才能调整。
代码实现:
// n指数组元素个数,root指初始的根节点下标。
//向下调整算法
//示例为大堆,(想变为小堆只要选出小孩子,将大父亲换下去)
//除了第一个数据,其他的都是正确的堆形式,可以用向下调整算法。变成正确的完整的堆
void ADjustDown(HPDatatype*arr, int n, int root)
{
int parents = root;
int child = parents * 2 + 1; //二叉树的左孩子是双亲节点的二倍加1
//二叉树的右孩子是双亲节点的二倍加2
while (child < n) //一次交换是不够的,可能会破坏下面的堆 。所以直到第一个数据找到合适的位置才停止。
{
if (arr[child] < arr[child+1] && child + 1 < n)//选出大孩子, 防止+1越界。
{
child++;
}
if (arr[child] > arr[parents]) //如果父亲小于孩子则交换,
{
swap(&arr[child], &arr[parents]);
parents = child; //下一次循环进来, 继续向该节点的孩子进行检测,是否破坏了原本的堆,所以要调整下标
child = parents * 2 + 1;
}
else
{
break; //找到了合适的位置。
}
}
}
1.3.2堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始向下调整算法,一直调整到根节点的树,就可以调整成堆。
代码实现:
void MyHeapCreate(Heap *php, HPDatatype *arr, int n)
{
assert(php);
//初始化
if (php != NULL)
{
//======================如果录入的数据是在单独接口录入,录入的数据是局部变量时,采用malloc保证生命周期。记得释放(Destory)
php->_arr = (HPDatatype*)malloc(sizeof(HPDatatype)*n);
memcpy(php->_arr, arr, sizeof(HPDatatype)*n);
//======================
//php->_arr = arr; //数据是使用时的全局变量时。直接指向即可,也不需要释放(Destory)。
php->_size = n;
php->_capacity = n;
}
//构建堆
//从最后一个双亲节点开始向下调整,直至第一个。当所有的双亲节点都调整好了,就是堆了。
int lastparents = (n - 1 - 1) / 2;//为什么减两次一呢? lastchild=n-1,之前证明了child=parents*2+1; lastparents=(lastchild-1)/2;
int i = 0;
for (i = lastparents; i >= 0; i--) // i要等于0,因为数组下标为0,而且a【0】是第一个双亲节点,一定要调整
{
ADjustDown(php->_arr, n, i);
}
}
1.3.2堆排序算法
创建好堆以后,我们会发现,堆顶这个数据是这个堆的最值,(大根堆为最大,小根堆为最小)我们将这个最值与最后一个数组元素交换,然后无视掉它,你会发现,刚好满足向下调整算法,使用向下调整算法后,又构造了一个新的堆顶2,再交换,向下调整。直到只剩一个节点,无法构成堆。
代码实现:
// 对数组进行堆排序
void HeapSort(HPDatatype *arr, int n)
{
int lastparents = (n - 1 - 1) / 2;
int i = 0;
for (i = lastparents; i >= 0; i--)
{
ADjustDown(arr, n, i);
}
int end = n - 1; //既是最后一个元素的下标,又是已经数组元素个数-1的数字。
while (end > 0)
{
swap(&arr[end],&arr[0]); //和最后一个元素交换
ADjustDown(arr, end, 0); //已经是无视掉最后一个元素了。因为我们的向下调整算法里,第二个参数的含义是:数组元素个数,
end--;
}
}
1.4完整堆的实现与演示代码
1.4.1 头文件:Heap.h
#pragma once
#include <stdio.h>
#include<Windows.h>
#include <assert.h>
typedef int HPDatatype;
typedef struct Heap
{
HPDatatype *_arr;
int _size;
int _capacity;
}Heap;
void swap(HPDatatype *a1, HPDatatype *a2);
void MyHeapCreate(Heap *php, HPDatatype *arr, int n);
void HeapPush(Heap *php, HPDatatype x);
void HeapPop(Heap *php);
HPDatatype HeapTop(Heap *php);
int MyHeapSize(Heap*php);
int HeapEmpty(Heap* php);
int HeapFull(Heap* php);
void HeapDestory(Heap *php);
// 对数组进行堆排序
void HeapSort(HPDatatype *arr, int n);
//向下调整算法
void ADjustDown(HPDatatype*arr, int n, int root);
//向上调整算法
void ADjustUp(HPDatatype*arr, int n, int root);
void PrintTopK(HPDatatype *arr, int n, int k);
void arrprint(Heap* php);
1.4.2 接口实现文件:Heap.c
#include "Heap.h"
//===关于堆,构造堆,堆排序等的理解博客:
void swap(HPDatatype *a1, HPDatatype *a2)
{
HPDatatype temp = *a1;
*a1 = *a2;
*a2 = temp;
}
void MyHeapCreate(Heap *php, HPDatatype *arr, int n)
{
assert(php);
if (php != NULL)
{
//======================如果录入的数据是在单独接口录入,录入的数据是局部变量时,采用malloc保证生命周期。记得释放(Destory)
php->_arr = (HPDatatype*)malloc(sizeof(HPDatatype)*n);
memcpy(php->_arr, arr, sizeof(HPDatatype)*n);
//======================
//php->_arr = arr; //数据是使用时的全局变量时。直接指向即可,也不需要释放(Destory)。
php->_size = n;
php->_capacity = n;
}
//构建堆
//从最后一个双亲节点开始向下调整,直至第一个。当所有的双亲节点都调整好了,就是堆了。
int lastparents = (n - 1 - 1) / 2;//为什么减两次一呢? lastchild=n-1,之前证明了child=parents*2+1; lastparents=(lastchild-1)/2;
int i = 0;
for (i = lastparents; i >= 0; i--) // i要等于0,因为数组下标为0,而且a【0】是第一个双亲节点,一定要调整
{
ADjustDown(php->_arr, n, i);
}
}
//尾插入以后,为了保证堆的顺序,进行一次向上调整即可。
void HeapPush(Heap *php, HPDatatype x)
{
assert(php);
if (HeapFull(php))
{
HPDatatype *a = (HPDatatype*)realloc(php->_arr, sizeof(HPDatatype)*php->_capacity * 2);
if (a != NULL)
{
php->_arr = a;
php->_capacity *= 2;
php->_arr[php->_size] = x;
php->_size++;
}
}
else
{
php->_arr[php->_size] = x;
php->_size++;
}
ADjustUp(php->_arr, php->_size, 0);
}
//删除堆顶:将堆顶换到最后,第一个数据不满足,再向下调整。调整结束后再删除。
void HeapPop(Heap *php)
{
assert(php);
if (HeapEmpty(php))
{
return;
}
else
{
swap(&php->_arr[0], &php->_arr[php->_size - 1]); //交换 //为什么减一? 减一是最后一个元素的下标。
ADjustDown(php->_arr, php->_size-1, 0); //向下调整 //为什么减一? 我们要无视最后一个,令他满足向下调整的条件,再进行调整。
php->_size--; //删除
}
}
HPDatatype HeapTop(Heap *php)
{
assert(php);
return php->_arr[0];
}
int MyHeapSize(Heap*php)
{
assert(php);
printf("%d\n", php->_size);
return php->_size;
}
int HeapEmpty(Heap* php)
{
assert(php);
if (php->_size==0)
{
return 1;
}
else
{
return 0;
}
}
int HeapFull(Heap* php)
{
assert(php);
if (php->_capacity == php->_size)
{
return 1;
}
else
{
return 0;
}
}
void HeapDestory(Heap *php)
{
assert(php);
free(php->_arr); //要注意,_arr是malloc出来的再释放,别瞎用。
php->_capacity = 0;
php->_size = 0;
php = NULL;
}
// 对数组进行堆排序
void HeapSort(HPDatatype *arr, int n)
{
int lastparents = (n - 1 - 1) / 2;
int i = 0;
for (i = lastparents; i >= 0; i--)
{
ADjustDown(arr, n, i);
}
int end = n - 1; //既是最后一个元素的下标,又是已经数组元素个数-1的数字。
while (end > 0)
{
swap(&arr[end],&arr[0]); //和最后一个元素交换
ADjustDown(arr, end, 0); //已经是无视掉最后一个元素了。因为我们的向下调整算法里,第二个参数的含义是:数组元素个数,
end--;
}
}
//向下调整算法
//示例为大堆,(想变为小堆只要选出小孩子,将大父亲换下去)
//除了第一个数据,其他的都是正确的堆形式,可以用向下调整算法。变成正确的完整的堆
void ADjustDown(HPDatatype*arr, int n, int root)
{
int parents = root;
int child = parents * 2 + 1;
while (child < n) //一次交换是不够的,可能会破坏下面的堆 。所以直到第一个数据找到合适的位置才停止。
{
if (arr[child] < arr[child+1] && child + 1 < n)//选出大孩子, 防止+1越界。
{
child++;
}
if (arr[child] > arr[parents]) //如果父亲小于孩子则交换,
{
swap(&arr[child], &arr[parents]);
parents = child; //下一次循环进来, 继续向该节点的孩子进行检测,是否破坏了原本的堆,所以要调整下标
child = parents * 2 + 1;
}
else
{
break; //找到了合适的位置。
}
}
}
//向上调整算法
//示例为建立大堆。(变成小堆只要当父亲大于孩子则交换)
void ADjustUp(HPDatatype*arr, int n, int root)
{
int child = n - 1;
int parents = (child - 1) / 2;
while (parents > root)
{
if (arr[child] > arr[parents]) //如果父亲小于孩子则交换,
{
swap(&arr[child], &arr[parents]);
child = parents; //下一次循环进来, 继续向该节点的孩子进行检测,是否破坏了原本的堆,所以调整下标
parents = (child - 1) / 2;
}
else
{
break; //找到了合适的位置。
}
}
}
void PrintTopK(HPDatatype *arr, int n, int k)
{
//=========三张方式,注意使用场景。================
//1、直接堆排序,求出所有排名,但是后面的排名没有用,当数据量特别大的时候,也无法排。
//2、建立一个堆,求出最值,这是第一个最值数据,当K>2时,求次最值,可以堆顶和最后一个数据交换,再向下调整,次最值到最后去了,无视size--,求k个最值,交换并K-1次。
//3、
}
void arrprint(Heap* php)
{
int i = 0;
if (php->_size == 0)
{
printf("空堆!");
}
for (i = 0; i < php->_size; i++)
{
printf("%4d", php->_arr[i]);
}
printf("\n");
}
1.4.3 测试文件:test.c
#include "Heap.h"
void Heaptest()
{
//==================堆测试代码=====================
HPDatatype arr[] = {
23, 65, 45, 31, 29, 66, 99, 66, 815, 1, 0 };
int n = sizeof(arr) / sizeof(arr[0]);
Heap php;
MyHeapCreate(&php, arr, n);
arrprint(&php);
MyHeapSize(&php);
HeapPush(&php, 2);
HeapPush(&php, 3);
HeapPush(&php, -1);
arrprint(&php);
MyHeapSize(&php);
HeapPop(&php);
HeapTop(&php);
arrprint(&php);
MyHeapSize(&php);
Heap* p = &php;
HeapSort(p->_arr,p->_size);
arrprint(&php);
MyHeapSize(&php);
HeapDestory(p);
}
int main()
{
Heaptest();
//=====堆排序测试代码
//HPDatatype arr[] = { 23, 65, 45, 31, 29, 66, 99, 66, 815, 1, 0 };
//int n = sizeof(arr) / sizeof(arr[0]);
/*HeapSort(arr, n);
int i = 0;
for (i = 0; i < n; i++)
{
printf("%4d",arr[i]);
}
printf("\n");*/
system("pause");
}