1、堆是一颗完全二叉树(适合使用顺序结构存储);
2、堆中的某个结点的值总是大于等于(最大堆)或小于等于(最小堆)其孩子结点的值。
3、堆中每个结点的子树都是堆树。
我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆(大堆也可以,这里用小堆举例)。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
void Swap(HPDataType* p, HPDataType* q)
{
HPDataType temp = *p;
*p = *q;
*q = temp;
}
//前提:左右子树都是小堆
void AdjustDown(HPDataType* a, int n, int root)
{
//找出左右孩子出小的一个
int parent = root;
int child = parent * 2 + 1;//左孩子
while (child < n)
{
//找出左右孩子中小的一个
if ((child + 1 < n) && a[child + 1] < a[child])
{
child = child + 1;
}
//如果孩子小于父亲就交换
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;//继续下调
child = parent * 2 + 1;
}
else
{
break;
}
}
}
堆的数据结构如下:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
基本思想:首先将每个叶子结点视为一个堆,再将每个叶子结点于其父节点一起构成一个包含更多结点的堆。所以在构造堆的时候,首先需要找到最后一个结点的父节点,从这个节点开始构造小堆,直到该节点前面的所有分支节点都处理完毕。
设当前元素在数组中以R[i]表示,那么:
1.它的左孩子结点是:R[2i+1];
2.它的右孩子结点是: R[2i+2];
3. 它的父结点是:R[(i-1)/2];
堆的初始化:
//初始化
void HeapInit(Heap* php, HPDataType* arr, int n)
{
php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
memcpy(php->_a, arr, sizeof(HPDataType) * n);
php->_size = n;
php->_capacity = n;
//构建堆
//i是从最后一个非叶子结点的索引开始的
for (int i = (n-1-1) / 2; i >= 0; i--)
{
AdjustDown(php->_a, n, i);//下调
}
}
第一层节点的个数为2^0个,单个节点向下调整的次数为h-1次
第二层的节点个数为2^1个,单个节点向下调整的次数为h-2次
第三层的节点个数为2^2个,单个节点的向下调整次数为h-3次
…
第h-1层的节点个数为2^(h-2)个,每个节点的向下调整次数为1次
即可求出:时间复杂度=2^0 * h-1+2^1 * (h-2)+2^2 * (h-3)+…+2^(h-2) * 1
通过h=logN和错位相减得到:时间复杂度=N-logN
即建堆的时间复杂度为:O(N)
堆的插入是在最后一个位置插入,就是数组的尾插,然后在进行向上调整。
//向上调整
void AdjustUp(HPDataType* a, int n, int child)
{
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;
}
}
}
//插入
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
//如果空间不够增容
if (php->_size == php->_capacity)
{
php->_capacity *= 2;
HPDataType* temp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);
php->_a = temp;
}
php->_a[php->_size++] = x;//赋值,size+1
AdjustUp(php->_a, php->_size, php->_size - 1);//传入
}
堆的删除是删头
//删头
void HeapPop(Heap* php)
{
assert(php);
assert(php->_size > 0);
Swap(&php->_a[0], &php->_a[php->_size - 1]);
php->_size--;
AdjustDown(php->_a, php->_size, 0);
}
//取堆顶的元素
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(php->_size > 0);
return php->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* php)
{
assert(php);
return php->_size;
}
// 堆的判空 1为空,0为非空
int HeapEmpty(Heap* php)
{
assert(php);
return php->_size == 0 ? 1 : 0;
}
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
堆是一个近似完全二叉树的结构,并同时满足堆积的性质:
即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序的平均时间复杂度为 Ο(nlogn)
排降序用小堆
void HeapSort(int* a, int n)
{
//1.排降序建小堆 2.排升序建大堆
//1.建堆,i为每个小堆的root
//时间复杂度O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//2.堆顶元素和最后一个元素互换,在对end-1个元素下调
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);//首尾互换,把最小的放到最后
//再继续选次小的
AdjustDown(a, end, 0);
--end;
}
}