堆排序,与归并排序一样,时间复杂度为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。
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 }
输出:
时间: 2024-10-02 04:15:08