基本概念 堆排序是一种树形选择排序方法,其特点:在排序过程中,将L[1…n]视为一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素
若n个关键字序列L[1…n]满足下面某一种性质,则称为堆(Heap) ①若满足: L(i) ≥ L(2i)且L(i)≥L(2i+1) (1 ≤ i ≤ n/2)-大根堆(大顶堆) ②若满足: L(i) ≤ L(2i)且L(i)≤L(2i+1) (1 ≤ i ≤ n/2)-小根堆(小顶堆)
显然,在大根堆中,最大元素存放在根结点中,且对其任一非根结点,它的值小于等于其双亲结点值。小根堆的定义刚好相反,根结点是最小元素。
堆经常被用来实现优先级队列,优先级队列在操作系统的作业调度和其他领域有广泛的应用
代码逻辑 建立堆栈 1.编号≤n/2的所有结点依次下坠调整(自低向上处理各分支节点) 2.小元素层逐层下坠(与关键字更大的孩子交换)
进行排序 1.将堆顶元素加入有序字队列(堆顶元素与堆低元素交换) 2.堆低元素换到堆顶后,需要进行下坠调整,恢复大根堆的特性 3.上述过程重复n-1趟
具体实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void BuildMaxHeap (ElemType A[], int len) { for (int i=len/2 ; i>0 ; i--) { AdjustDown(A,i,len); } }void AdjustDown (ElemType A[], int k, int len) { A[0 ] = A[k]; for (i = 2 *k; i <= len; i *= 2 ) { if (i < len && A[i] < A[i+1 ]) { i++; } if (A[0 ] >= A[i]) { break ; } else { A[k]=A[i]; k=i; } } A[k]=A[0 ]; }void HeapSort (ElemType A[], int len) { BuildMaxHeap(A, len); for (i=len; i>1 ; i--) { Swap(A[i],A[1 ]); AdjustDown(A,1 ,i-1 ); } }
性能分析 时间复杂度 建堆时间为O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为O(h),排堆的时间复杂度为O(nlog2n)故在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2n)
空间复杂度 仅使用了常数个辅助单元,所以空间复杂度为O(1)
稳定性 进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序方法
插入删除 插入操作 1.新元素放到表尾(堆底) 2.根据大/小堆栈的要求,新元素不断“上升”,直到无法继续上升为止
1 2 3 4 5 6 7 8 9 10 11 12 void AdjustUp (ElemType A[], int k) { A[0 ]=A[k]; int i=k/2 ; while (i>0 &&A[i]<A[0 ]) { A[k]=A[i]; k=i; i=k/2 ; } A[k]=A[0 ]; }
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 typedef int E;typedef struct MinHeap { E * arr; int size; int capacity; } * Heap;_Bool initHeap (Heap heap) { heap->size = 0 ; heap->capacity = 10 ; heap->arr = malloc (sizeof (E) * heap->capacity); return heap->arr != NULL ; }_Bool insert (Heap heap, E element) { if (heap->size == heap->capacity) return 0 ; int index = ++heap->size; while (index > 1 && element < heap->arr[index / 2 ]) { heap->arr[index] = heap->arr[index / 2 ]; index /= 2 ; } heap->arr[index] = element; return 1 ; }E delete (Heap heap) { E max = heap->arr[1 ], e = heap->arr[heap->size--]; int index = 1 ; while (index * 2 <= heap->size) { int child = index * 2 ; if (child < heap->size && heap->arr[child] > heap->arr[child + 1 ]) child += 1 ; if (e <= heap->arr[child]) break ; else heap->arr[index] = heap->arr[child]; index = child; } heap->arr[index] = e; return max; }int main () { int arr[] = {3 , 5 , 7 , 2 , 9 , 0 , 6 , 1 , 8 , 4 }; struct MinHeap heap ; initHeap(&heap); for (int i = 0 ; i < 10 ; ++i) insert(&heap, arr[i]); for (int i = 0 ; i < 10 ; ++i) arr[i] = delete (&heap); for (int i = 0 ; i < 10 ; ++i) printf ("%d " , arr[i]); }
详细过程 1.首先将给定的数组调整为一个大顶堆
2.进行N轮选择,每次都选择大顶堆顶端的元素从数组末尾开始向前存放(交换堆顶和堆的最后一个元素)
3.交换完成后,重新对堆的根结点进行调整,使其继续满足大顶堆的性质,然后重复上述操作。
4.当N轮结束后,得到的就是从小到大排列的数组了。