前言:今天我来介绍下堆排序,在写堆排序代码之前,我们要知道堆的概念!
堆的定义:n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n),当然,这是小根堆,大根堆则换成>=号。//k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点,k(2i+1)是右子节点
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
堆的分类:
1).大根堆和小根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。
2).根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。
注意:①堆中任一子树亦是堆。②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k
堆排序的思想:
1).先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
2).再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
3).由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
堆排序的参考代码:
以下代码参考算法导论和百度百科!
1 //堆排序,伪代码 2 // HEAPSORT(A) 3 // BUILD-MAX-HEAP(A) 4 // for i=A.length downto 2 5 // exchange A[1] with A[i] //将第一个元素和第i个元素交换 6 // A.heap-size = A.heap-size - 1; 7 // MAX-HEP-APIFY(A,1); 8 9 void heapAdjust(int iarr[],int start,int len) 10 { 11 int i = start,temp = 0,maxChildIndex = 0; 12 for(;i < len / 2; ++i) 13 { 14 maxChildIndex = 2*i+1; 15 if(maxChildIndex + 1 < len) //如果右节点存在 16 { 17 maxChildIndex = iarr[maxChildIndex] > iarr[maxChildIndex + 1] ? maxChildIndex: maxChildIndex + 1; 18 } 19 if(iarr[i] < iarr[maxChildIndex]) 20 { 21 temp = iarr[maxChildIndex]; 22 iarr[maxChildIndex] = iarr[i]; 23 iarr[i] = temp; 24 } 25 } 26 } 27 void heapSort(int iarr[],int len) 28 { 29 int i,temp; 30 //开始的时候建堆,根据堆的性质,从原始数组的最后一个元素的父节点开始调整即可 31 for(i = len / 2; i > 0; --i) 32 { 33 heapAdjust(iarr,i - 1,len); 34 } 35 for(i = 0;i < len; ++i) 36 { 37 cout << iarr[i] << " "; 38 } 39 cout << endl; 40 for(i = len - 1; i > 0; --i) 41 { 42 //堆的最后一个元素与第一个元素交换 43 temp = iarr[i]; 44 iarr[i] = iarr[0]; 45 iarr[0] = temp; 46 heapAdjust(iarr,0,i); 47 } 48 } 49 int main() 50 { 51 int a[10]={4,1,3,2,16,9,10,14,8,7}; 52 heapSort(a,10); 53 for(int i=0;i < 10; ++i) 54 cout << a[i] << " "; 55 return 0; 56 }
算法分析:
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用heapAdjust实现的。
平均性能:O(N*logN) = O(N)(heapSort中的两个for循环的时间复杂度)*O(lonN)(heapAdjust中的for循环的时间复杂度)。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1).
它是不稳定的排序方法。