堆排序,与归并排序同样,时间复杂度为O(nlgn),与插入排序同样,具备空间原址性:任什么时候候都只须要常数个额外的元素空间存储临时数据。(二叉)堆是一个数组,能够被当作一个近似的彻底二叉树,树上的每个结点对应数组中的一个元素。除了最底层外,该树是彻底充满的,并且是从左向右填充。表示堆的数组A有两个属性:A.length为数组元素的个数,A.heap-size表示有多少个堆元素存储在数组中。树的根结点为A[0],容易获得父结点、左孩子和右孩子的下标:
二叉堆分为最大堆和最小堆。结点的值都要知足堆序性质,最大堆中,堆中的最大元素存放在根结点中;而且,在任一子树中,该子树所包含的全部结点的值都不大于该子树根结点的值,最小堆性质相反。
堆的基本过程:
Max-Heapify过程:时间复杂度为O(lgn),是维护最大堆性质的关键。
Build-Max-Heap过程:具备线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆。
HeapSort过程:时间复杂度为O(nlgn),功能是对一个数据进行原址排序。
Max-Heap-Insert、Heap-Extract-Max、Heap-Increase-Key和Heap-Maximum过程:时间复杂度为O(lgn),功能是利用堆实现一个优先队列。
Max-Heapify的输入为一个数组A和一个下标i,经过让A[i]的值在最大堆中“逐级降低",从而使得如下标i为根结点的子树从新遵循最大堆的性质,可经过递归和非递归两种方式实现,代码以下:
1 void 2 percDownRecursion(int a[],int i, int n) // max-heapify 下滤 维护最大堆性质 递归方式 3 { 4 int left = left(i); 5 int right = right(i); 6 int large; 7 if(left < n && a[left] > a[i]) 8 large = left; 9 else 10 large = i; 11 if(right < n && a[right] > a[large]) 12 large = right; 13 if(large != i){ 14 swap(a[i],a[large]); 15 percDownRecursion(a,large,n); 16 } 17 }
1 void percDown(int a[],int i, int n) //max-heapify 下滤 维护最大堆性质 非递归方式 2 { 3 int child; 4 int tmp; 5 6 for(tmp = a[i]; left(i) < n; i = child) 7 { 8 child = left(i); 9 if(child != n-1 && a[child + 1] > a[child]) 10 child++; 11 if(tmp < a[child]) 12 a[i] = a[child]; 13 else 14 break; 15 } 16 a[i] = tmp; 17 }
建堆,能够用自底向上的方法利用过程Max-Heapify把一个大小为n的数组A转换为最大堆,A[n/2]后的元素都是树的叶结点,每一个叶结点均可以当作只包含一个元素的堆,时间复杂度为O(n)。。算法
1 void buildMaxHeap(int a[], int n) 2 { 3 for(int i = n/2; i >= 0; i--) // build heap 构建最大堆 4 percDownRecursion(a,i,n); 5 }
堆排序,堆排序算法利用Build-Max-Heap将输入数组A建成最大堆,由于数组中的最大元素总在根结点A[0]中,经过把它与A[n-1]进行互换,可让该元素放到正确的位置,缩减堆的大小并进行下滤,从而在A[0...n-2]上构造一个新的最大堆。堆排序算法会不断重复这一过程,直到堆的大小从n-1降到1。api
1 void 2 heapSort(int a[], int n) 3 { 4 buildMaxHeap(a,n); 5 for(int i = n-1; i > 0; i--) 6 { 7 swap(a[0],a[i]); 8 percDownRecursion(a,0,i); //或者调用percDown(a,0,i) 9 } 10 }
例子:数组
1 int main() 2 { 3 int a[] = {97,53,59,26,41,58,31}; 4 int size = 7; 5 heapSort(a,size); 6 for(int i=0; i < size; ++i) 7 printf("%d ",a[i]); 8 printf("\n"); 9 }
输出:ui