【DS】6.堆
堆(Heap)
数据集合如果有序,会为各种操作带来遍便利。但是有些应用并不要求数据全部有序,或者在操作开始之前就完全有序。我们期望的数据结构支持插入操作,并且能够方便的从中取出具有最小和最大关键码的记录,这样的数据结构就是优先队列。而实现优先队列最高效的一种数据结构就是堆。
在计算机科学中,堆是一种已完全二叉树为基础的满足队性质的特殊数据结构。(因此可以使用数组存储)
堆性质:如果B是A的子结点,那么A中的关键码就大于B中的关键码。这意味着结构中的数值最大的元素总是位于树的根部,这样的堆称为“最大堆”,同理也有“最小堆”。
建立堆的有效方法:将数据表中的元素顺序的填入一个完全二叉树中,然后通过所谓的“自上而下”(Top-Down)调整算法来使该完全二叉树满足堆性质。(1.通过构造函数建立一个空堆,其大小由动态分配得到;2.复制记录一个数组,对其加以调整得到)
堆的操作:
1)插入:插入一个元素总是插入在堆的最后面,然后再调整。
2)删除:通常是删除具有最小(最大)关键码的元素,也就是根结点,为了保持树形结构不会遭到破坏,在删除之后我们将堆的最后一个结点取走用来填补却是的堆顶元素,然后调整;(实际上是以数组进行存储,可以删掉任何一个元素,删除之后,元素数量减1)
关键代码:
/**
* 目标:将一个数组调整成为一个堆
* */
public class Heap {
static int heap[] = new int[20];
public static void main(String[] args) {
heap[0] = 3;
heap[1] = 5;
heap[2] = 2;
heap[3] = 7;
heap[4] = 10;
heap[5] = 4;
heap[6] = 1;
heap[7] = 9;
heap[8] = 15;
heap[9] = 18;
heap[10] = 11;
heap[11] = 8;
shiftDown(0, 12);
for(int i = 0; i < 12; i++){
System.out.print(heap[i] + " ");
}
}
public static void shiftDown(int start, int count){
int shiftStart = (count -2) / 2;//算出一棵树需要调整的位置
if(start > shiftStart)
return;//此时不需要调整,因为start之后的元素都没有子节点
else{
//从根节点往下遍历,形成最小堆
for(int sum = shiftStart; sum >=start; sum --){
int temp = heap[sum];
int index = sum;
int leftIndex = index *2 + 1;
while(leftIndex < count){
//从start开始往下遍历,直到最后一个元素
if(leftIndex < count-1){
if(heap[leftIndex] < heap[leftIndex + 1]){
leftIndex++;//指向子结点中较小的
}
}
if(leftIndex < count && temp < heap[leftIndex]){//这里一定要用temp
heap[index] = heap[leftIndex];
index = leftIndex;
leftIndex = index*2+1;
}else{
break;
}
}
heap[index] = temp;
}
}
}
}
代码备注:这里使用的建立堆的方法是,复制一个数组,然后对数组中的元素进行调整,最后形成一个堆。
shiftDown是一种自上而下的调整算法。其基本思想是:对有m个记录的集合R,将它置为完全二叉树的顺序存储。首先从结点i开始往下调整,前提条件是假定它的两棵子树都已经成为堆。但是很明显,对于任意一个传入的数组,一个结点的子树是不是堆不确定,因而仅仅使用一次shiftDown是得不到正确的堆的。
shiftDown可以做到使一个三个或者两个元素组成二层二叉树转换成一个堆,因而此处可以使用递归,对一个元素进行堆调整,先要对其左右子树使用shiftDown,以上代码将其改造成非递归算法。
shiftDown函数从shiftStart处开始,往start点不断靠拢,当start=0,就是将整个树调整为堆。这样就能保证每次调整的时候结点的左右结点都是堆。这里是自下而上的方法。
补充说明:shiftDown只能将一个集合的局部调整为堆,而shiftUp算法一般适用于堆的插入算法,当然也可以在heap最后添加一个元素,然后丢入以上函数,但这样比较费时,下面列出shiftUp的代代码:
public void shiftUp(int start){
int j = start;
int i = (j - 1 ) / 2;
int temp = heap[j];//就是插入的那个元素
while(j > 0){
if(heap[i] <= temp)
break;
else{
heap[j] = heap[i];
j = i;
i = (i - 1)/2;
}
}
heap[j] = temp;
}
删除元素的时候则需要将最后一个元素提前到最前面,然后调用shiftDown算法从堆顶向下调整,下面也给出纯shiftDown算法,注意,此时是满足shiftDown算法的前提条件:
public void shiftDown(int start, int m){
int i = start;
int j = i * 2 + 1;
int temp = heap[j];//就是插入的那个元素
while(j <= 0){
if(j < m && heap[i] > heap[i+1])
j++;
if(temp <= heap[j])
break;
else{
heap[i] = heap[j];
i = j;
j = 2 * j + 1;
}
}
heap[j] = temp;
}
(以上两段代码来自殷人昆的《数据结构》)
补充分析:
从完全二叉树的性质可以知道,N个结点的完全二叉树的深度为K = log2(n+1)(取整+1),应用堆的调整算法的shiftDown时,while循环次数最大为深度减1,所以堆算法的时间复杂度为O(log2n)。而在插入一个新结点的时候,使用一个对的调整算法shiftUp,时间复杂度相同。建树操作执行了N/2次shiftDown操作,其时间复杂度为O(nlog2n)。

浙公网安备 33010602011771号