【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)。

posted @ 2012-08-10 10:45  大脚印  阅读(269)  评论(0)    收藏  举报