堆以及堆的实际应用
堆以及堆的实际应用
1.堆的概念
堆的存储可以看成是数组存储的变形,不过,堆的存储又具有二叉树的结构,堆的存储按类型可以分为大堆和小堆。小堆(大堆)中:任一节点的关键码均小于(大于)等于它的左右孩子的关键码,位于堆顶节点的关键码最小(最大),从根节点到每个结点的路径上数组元素组成的序列都是递增(递减)的。
2.堆的创建
堆可以看做是由一个数组转化而来的。因此,堆的创建也可以进行一些转化
第一步,将一个数组里面的元素通过下标按层·排序的方式构建一个完全二叉树
第二步·,将这个二叉树通过向下调整的方法调整成为最小堆
原理:从最后一个非叶子结点开始调整,一直到根节点为止,将每个结点及其子树调整到满足小堆的性质即可。
具体调整方法:
1.假设该节点的下标为parent
2.找到该节点的左孩子left=parent*2+1
3.如果右孩子right=parent*2+1存在,找到left和right之中最小的孩子
4.比较parent是否小于其左右孩子较小者,如果小于等于较小者则调整结束
否则将parent中元素与较小的孩子交换,如果调整过后不满足小堆性质,那就继续调整直到子树满足性质即可。
3.堆的插入和删除
由于堆具有特殊的性质,因此它的插入和删除比一般的二叉树要麻烦很多
1。堆的插入:在已经建成的最小堆的后面插入一个新的元素,插入之后,插入之后,如果树中节点不满足堆的性质时,就需要进行调整。
方法
由于已经是小堆,因此,需要调整的只是从该叶子节点到根节点的子树。先找到该节点的父亲节点,如果小于父亲节点,那么就需要进行向上调整,使其调整之后满足小堆的性质,如此重复多次,直到整个堆都满足小堆的性质
2.堆的删除:堆的删除也是不可以随便乱删的,因为随便删除的话会完全破坏小堆的结构,使其不再具备小堆的性质,如果想要保持这种性质,就需要重新建堆,那么时间复杂度将会很大。
方法
将堆中最后一个元素替代堆顶元素
由于堆的元素个数是按下标控制,因此只需要将下标减一就可以将堆中最后一个元素删除
此时,如果堆的结构被破坏,只需要进行简单的向下调整就可以使其重新满足小堆的性质,而其时间复杂度只有log N,因此用堆来进行排序也是可以行得通的。
堆的应用
1.优先级队列
思路分析:优先级队列可以用排队这个实际现象来说明。当你要去排队买饭的时候,如果去得早,那么就可以很快买到,如去的晚,那么就要排在别人的后面,你也就很晚才能买到,这时候还需要考虑一个问题,如果有的人特别慢,那就需要考虑优先级了。优先级的特点就是给较小的数字设置较高的优先级那么就可以很快的访问到,数据的处理也就可以更加快速我把代码放在下面。
void PriorityQueueInit(PriorityQueue* q)
{
assert(q);
q->_size=0;
memset(q->_a,0,sizeof(DataType)*N);
}
void PriorityQueuePush(PriorityQueue* q, DataType x)
{
assert(q);
if(q->_size>=N)
{
printf("PriorityQueuefull\n");
return;
}
q->_a[q->_size++]=x;
AdjustUp(q->_a,q->_size,q->_size-1);
}
void PriorityQueuePop(PriorityQueue* q)
{
assert(q);
if(NULL==q)
{
printf("PriorityQueueEmpty\n");
return;
}
q->_a[0]=q->_a[q->_size-1];
q->_size--;
AdjustDown(q->_a,q->_size,0);
}
DataType PriorityQueueTop(PriorityQueue* q)
{
assert(q);
if(NULL==q)
{
printf("PriorityQueueEmpty\n");
return;
}
return q->_a[0];
}
size_t PriorityQueueSize(PriorityQueue* q)
{
assert(q);
return q->_size;
}
size_t PriorityQueueEmpty(PriorityQueue* q)
{
assert(q);
return q->_size;
}
void HeapSort(DataType* a, size_t n)
{
int i=0;
assert(a);
MakeHeap(a,n);
while(n>0)
{
printf("%d ",a[0]);
a[0]=a[n-1];
n--;
AdjustDown(a,n,0);
}
2.100亿个数中找出最大的前k个数(海量数据topk问题)
思路分析:乍一看,100亿个数,确实很大,一个数占四个字节,那么100亿个数就需要40G的存储空间,这对与普通电脑来说确实是不可能的。但是,这道题肯定不可能让我们创建一个具有100亿个数据的堆,这样不说存储空间不够大,时间复杂度也是很大的,正确·的做法是,
1.创建一个有k个元素的小堆
2.利用循环,将最后一个元素和第一个元素进行替换,然后进行向下调整使其满足小堆的性质,如果这个数比根节点都小,那就直接舍弃,否则进行调整,直到这个堆里面所有的数据都是你要找的最大的那几个数为止,这样再进行打印就可以了
主要代码如下:
#include"Heap.h"
void MakeHeap(DataType* a, size_t n)//构建堆
{
int i=(n-1)>>1;
for(;i>=0;i--)
{
AdjustDown(a,n,i);
}
}
void AdjustDown(DataType* a, size_t n, int root)//向下调整
{
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])
{
DataType tmp;
tmp=a[child];
a[child]=a[parent];
a[parent]=tmp;
parent=child;
child=2*parent+1;
}
else
{
break;
}
}
}
void AdjustUp(DataType* a, size_t n, int child)//向上调整
{
int parent=(child-1)>>1;
while(child>0)
{
if(a[child]<a[parent])
{
DataType tmp;
tmp=a[child];
a[child]=a[parent];
a[parent]=tmp;
child=parent;
parent=(child-1)>>1;
}
else
{
break;
}
}
}
void TopK(DataType* a, size_t n, size_t k)
{
int i;
MakeHeap(a,k);//建小堆
for(i=k;i<n;i++)
{
a[0]=a[i];
AdjustDown(a,k,0);
}
for(i=0;i<k;i++)
{
printf("%d ",a[i]);
}