堆排序和优先级队列
堆排序:和合并排序一样,时间复杂度为O(nlgn);同时和插入排序一样,在原序列中进行;这样堆排序集合了合并排序和插入排序的优点。
堆排序的另一个特点是利用了"堆"这种数据结构. 堆数据结构还不止在堆排序中有用,还可以构成一个有效的优先队列.
堆: 是一种数据结构,也是一种数组对象,如图 1-1所示:
图 1-1 最大堆(图片来源《算法导论》)
如上图1-1 所示, 可以被看成一棵完全二叉树,二叉树的的每个节点和数组中存放该节点的那个元素对应.在使用"堆"数据机构的时候,其实并不需求真正构建一棵完全二叉树, 数据的存储依然是在一个数组中,操作也在数组中,只是数组中数据的存放,位置关系如完全二叉树所示,我们在构造堆的时候其实是在构建数组中数据的关系. 上图所示一个最大堆(父节点大于两个子节点), 最小堆同理.
由于我们将数组中的数据关系对应成了一块完全二叉树,所以对数组的各种操作时间复杂度也应该和完全二叉树操作向对应,即O(nlgn). 下文所以的堆即代指"最大堆"。
堆排序
根据堆的性质,父节点大于子节点,那么根节点一定是最大的,继而数组中第一个数据也应该是最大的. 根据冒泡排序和选择排序的原理, 如果将每次数组中的第一个元素和最后一个元素进行交换,再让第一个到倒数第二个形成的新数组重新形成最大堆,再次将新数组第一个和最后一个交换;需要重新建堆的数组长度不断减小.如此循环下去,最后原数组成了一个有小到大的有序序列.
根据上面的论述,如何让无序的元素,构建成一个符合(最大/最小)堆性质的堆才是关键.
保持堆(最大堆)性质原理: 将父节点和两个子节点进行比较,找到那个最大;如果父节点最大,该节点符合最大堆性质;如何是两个子节点中某个最大,将最大子节点和父节点位置进行交换; 在查看新的子节点作为父节点是否符合最大堆的性质,以此循环.
代码如下:
1 //子节点和父节点在数组中位置的对应关系(数组从0开始) 2 int parent(int i) 3 { 4 return (int)((i-1)/2); 5 } 6 //父节点和左子节点在数组中的位置对应关系 7 int Left(int i) 8 { 9 return 2*i+1; 10 } 11 //父节点和右子节点在数组中的位置对应关系 12 int Right(int i) 13 { 14 return 2*i+2; 15 } 16 17 //保持最大堆性质,循环控制的形式 18 void MaxHeapify(int A[], int i,int length) 19 { 20 int left,right,largest; 21 left=Left(i); 22 right=Right(i); 23 24 largest=length; 25 26 while(largest!=i){ 27 28 if(left<length && A[left]>A[i]) 29 largest=left; 30 else 31 largest=i; 32 if(right<length && A[right]>A[largest]) 33 largest=right; 34 int temp; 35 temp=A[largest]; 36 A[largest]=A[i]; 37 A[i]=temp; 38 } 39 40 } 41 42 //保持最小堆性质,递归的形式 43 void MinHeapify(int A[], int i,int length) 44 { 45 int left,right,minmun; 46 left=Left(i); 47 right=Right(i); 48 49 if(left<length && A[left]<A[i]) 50 minmun=left; 51 else 52 minmun=i; 53 if(right<length && A[right]<A[minmun]) 54 minmun=right; 55 56 if(minmun!=i) 57 { 58 int temp; 59 temp=A[minmun]; 60 A[minmun]=A[i]; 61 A[i]=temp; 62 63 MaxHeapify(A,minmun,length); 64 } 65 66 }
建堆以及堆排序算法:
堆排序的基本原理就是对一个无序数组建立堆关系,再利用堆的性质不断重数组中查找最值元素.
建堆的代码如下:
1 void BuildMaxHeapify(int A[], int length) 2 { 3 int i; 4 for(i=(int)((length-1)/2);i>=0;i--) 5 MaxHeapify(A,i,length); 6 }
堆排序的算法代码如下:
1 void Heapsort(int A[],int Length) 2 { 3 int i; 4 int temp; 5 BuildMaxHeapify(A,Length); 6 7 for(i=Length;i>=1;i--) 8 { 9 MaxHeapify(A,0,i); 10 11 temp=A[i-1]; 12 A[i-1]=A[0]; 13 A[0]=temp; 14 15 } 16 17 }
优先级队列
优先级队列分为最大优先级队列和最小优先级队列.(应用举例摘于《算法导论》)
最大优先级队列应用:其一,一台分时计算机上进行作业调度,如何从具有优先级标示的队列中选择优先级最高的任务.
最小优先级队列应用: 其一, 被用在基于事件驱动的模拟器中. 在这种应用中,队列中的各项是要模拟的事件,每一个都有一个发生时间作为关键字。事件模拟按照各事件的发生时间的顺序进行,因为模拟某一事件可能导致稍后对其他事件的模拟。模拟程序都使用EXTRACT-MIN来选择下一个事件,而使用INSERT将一个新事件插入队列中。
最大优先级队列提取操作代码如下:
1 int HeapExtractMax(int A[], int length) 2 { 3 int max; 4 5 if(length<1) 6 printf("the heap underflow! "); 7 else 8 { 9 max=A[0]; 10 A[0]=A[length-1]; 11 12 MaxHeapify(A,0,length); 13 } 14 15 return max; 16 }
最大优先级队列提高或减小优先级操作代码如下:
1 void HeapIncreaseKey(int A[],int i,int key) 2 { 3 int temp; 4 5 if(key<A[i]) 6 { 7 printf("The new key can not be smaller than current key! "); 8 } 9 else 10 { 11 while(i>=0 && A[parent(i)]<key) 12 { 13 temp=A[parent(i)]; 14 A[parent(i)]=key; 15 key=temp; 16 17 i=parent(i); 18 } 19 } 20 } 21 22 //减小某一元素的关键字 23 void HeapDecreaseKey(int A[],int i,int key,int length) 24 { 25 int temp; 26 27 if(key>A[i]) 28 { 29 printf("The new key can not be bigger than current key! "); 30 } 31 else 32 { 33 while(i<length && (key<A[Left(i)] || key<(Right(i)))) 34 { 35 if(key<A[Left(i)]) 36 { 37 temp=A[Left(i)]; 38 A[Left(i)]=key; 39 key=temp; 40 41 i=Left(i); 42 } 43 else 44 { 45 temp=A[Right(i)]; 46 A[Right(i)]=key; 47 key=temp; 48 49 i=Right(i); 50 } 51 52 } 53 } 54 55 }
最大优先级队列插入新元素操作代码如下:
1 void MaxHeapInsert(int A[],int key,int length) 2 { 3 length=length+1; 4 A[length-1]=-1;//假设数组内的整数全部大于0 5 6 HeapIncreaseKey(A,length-1,key); 7 }