堆的概念:
堆实际是一棵完全二叉树,满足任意节点的值都大于(小于)其子树节点的值,称作大堆(小堆)
堆的存储结构:
使用数组保存二叉树结构,方法即是将二叉树按层序遍历方式放入数组中
堆的下标关系:
- 已知父节点的下标parent:左孩子(left)下标=parent2+1、右孩子(right)下标=parent2+2
- 已知孩子下标(child)不分左右:父节点下标(parent)=(child-1)/2
补充:
完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树
堆的示意图:
堆的操作:
向下调整(注意只有当局部(即左右子树)为堆时,当前节点才能向下调整)
小堆:
//向下调整(前提:左右子树必须是一个堆,才能向下调整
public static void shifDownSmall(int[] array,int size,int parent){
//此结点与其孩子结点进行比较,若孩子结点比父节点小,则交换,否则不变
//交换节点后下标相应改变,被交换结点的下标继续作为父节点,与其双亲做比较
int left=(parent*2)+1;
while(left<size){
//两个孩子结点中选一个最大的
if(left+1<size&&array[left+1]<array[left]){
left++;
}
if(array[left]<array[parent]){
swap(array,left,parent);
//更新,继续向下调整
parent=left;
left=(parent*2)+1;
}else{
break;
}
}
}
public static void swap(int[] arr,int x,int y){
int temp=arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
大堆:
public static void shiftDownBig(int[] array,int size,int parent){
int left=(parent*2)+1;
while(left<size){
//两个孩子结点中选一个最大的
if(left+1<size&&array[left+1]>array[left]){
left++;
}
if(array[left]>array[parent]){
swap(array,left,parent);
parent=left;
left=(parent*2)+1;
}else{
break;
}
}
}
建堆
注意:
此时堆的左右子树既不是大堆也不是小堆,若还是从根结点开始调整,则无法确定调整次数,以及具体算法
因此要从最小子树开始调整,使局部调整为子树,然后再在局部的基础上才能对上面的结点实现向下调整
小堆:
public static void createSmallHeap(int[] array){
for(int i=(array.length-2)/2;i>=0;i--){
shifDownSmall(array,array.length,i);
}
}
大堆:
public static void createBigHeap(int[] array){
for(int i=(array.length-2)/2;i>=0;i--){
shiftDownBig(array,array.length,i);
}
}
堆的应用—优先级队列:
优先级队列:
数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列
PriorityQueue pq=new PriorityQueue<>();
两个基本操作:
入队列(堆的向上调整):
- 首先按尾插方式放入数组
- 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
- 否则,交换其和双亲位置的值,重新进行 2、3 步骤
- 直到根结点
小堆
//向上调整,例如在原有的堆中加入一个元素(实现优先级队列)可模仿入队过程
//只需要和根节点比较
//然后更新节点按位置
public static void shitUpSmall(int[] array,int size,int child){
int parent=(child-1)/2;
while(child>0){
//变换条件实现大堆:array[child]>array[parent]
if(array[child]<array[parent]){
swap(array,child,parent);
child=parent;
parent=(child-1)/2;
}else{
break;
}
}
}
public void offer(int value){
if(sz==arr.length){
arr=Arrays.copyOf(arr,arr.length*2);
}
//1.尾插
//2.向上调整
arr[sz++]=value;
MyHeap.shifUpSmall(arr,sz,sz-1);
//加入最后一个元素进行向上调整
}
出队列:
为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过
向下调整方式重新调整成堆
public int poll(){
if(sz>0){
int tmp=arr[0];
arr[0]=arr[sz-1];
--sz;
MyHeap.shifDownSmall(arr,sz,0);
return tmp;
}else{
return -1;
}
}
返回队顶元素:
public int peek(){
return arr[0];
}
堆的应用还有堆排序,在海量元素中找前k个最大等应用,目前掌握还不是很好,在后期文章中会介绍。
结语:如有不足,请多指教。