堆及堆应用
堆的定义
(二叉)堆是一个数组,可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素,如图
假若A[i]为堆的一个结点,则他的左右子节点为A[2*i], A[2*i+1],根结点为A[1]。即A[1]的子结点为A[2]和A[3]
堆可以分为最大堆和最小堆
最大堆:最大堆中某个结点的值必须小于等于其父结点的值,即A[parent(i)] >= A[i]
最小堆:最小堆中某个结点的值必须大于等于其父结点的值,即A[parent(i)] <= A[i]
堆的维护
如图(a)中,违背了最大堆的性质,4 < 14 ,但4却是14的父亲,所以需要维护堆,经过(b),使得变成 (c) 这种情况,满足最大堆中某个结点的值必须小于等于其父结点的值
维护堆的代码执行过程:时间复杂度为O(logn)
//该函数作用为了将i点及相邻的子结点中最大的点放到i点,以此来维护最大堆的性质
void MaxHeapify(int *A, int i, int n) //A数组存放堆值,i点要进行的堆维护,n结点的总数量,n为全局变量
{
int l = i << 1; //i结点的左结点
int r = i << 1 | 1 ; //i结点的右结点
if (l <= n && A[i] < A[l]) { //左孩子的值比i点的值大
largest = l;
}
else {
largest = i;
}
if (r <= n && A[largest] < A[r]) {
largest = r; //右孩子的值比i点的值大
}
if (largest != i) {
swap(A[largest], A[i]); //交换值
MaxHeapify(int *A, int largest); //继续向以largest为结点的堆维护最大堆的性质
}
}
建堆
建堆是将一个无序数组构造成一个最大堆(最小堆反之),是最大堆,而非有序堆
建堆代码:时间复杂度为O(n)
void BuildMaxHeap(int *A) //A数组存放堆值,n结点的总数量,n为全局变量
{
for(int i = n/2; i>=1; i--){
MaxHeapify(A, i); //进行堆维护
}
}
建堆过程如图:
堆排序
堆排序:将堆中没有排序的点与A[1]交换,在进行对维护,使得A[1]又是在没有排序的堆中最大,循环该操作,直到A[1]点最小为止
堆排序代码:
void HeapSort(int *A) //n结点的总数量,n为全局变量
{
BuildMaxHeap(A, n); //建堆
for(int i = n; i >= 2; i--){
swap(A[i],A[1]); //交换A[i]和A[1]的值
MaxHeapify(A, 1,n-1); //堆维护
}
}
堆排序过程:黑色表示已经排完序的
(a)中16先和1对换,1再进行堆维护,变成(b),循环往复,得到(j),得到有序数组(k)
堆的应用-优先队列
优先队列的实现原理是堆
优先队列分最大优先队列和最小优先队列
我们以最大优先队列为例,并且支持以下操作,下列操作代码都是底层代码
返回队列中最大值:
int HeapMaxValue(int *A)
{
return A[1];
}
返回队列中最大值,并且将该值从队列中删去:
int HeapExtractMaxValue(int *A) //n是全局变量,代表结点个数
{
if(n < 1) {
printf("heap underflow!");
}
int max = A[1];
A[1] = A[n];
n--;
MaxHeapify(A, 1); //堆维护
return max;
}
改变某个叶子结点的值:
void HeapChangeKey(int *A, int i, int key)
{
A[i] = key;
while( i > 1 && A[i>>1] < A[i]) { //为了维护堆的性质
swap(A[i>>1], A[i]); //交换
i = i >> 1;
}
}
改变某个结点的值,图过程:将4变成15,在与父亲结点进行比较,进行堆维护
插入值:
void HeapInsert(int *A, int key) //n是全局变量,代表结点个数
{
n++;
A[n] = key;
HeapChangeKey(int *A, int n, int key);
}