堆:是一种完全二叉树的数据结构,并且具有每个非叶子节点(索引i,从0开始的话)的值大于其左右子节点(2*i+1)的值(大根堆),或者小于其左右子节点(2*i+2)的值(小根堆)。堆的性质决定了其根节点的值必然整个堆中的最大值或者最小值,因此使用堆的这个特性,发明了堆排序。
堆排序的大致原理:
1. 建立一个堆,假如现在有一个需要被排序的数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0},建堆的过程就是不断调整每个节点的过程,使每个节点满足堆的特性。例如有一个最简单的堆{1,3,2},我们需要调整它成大根堆,我们就需要将父节点的值1和左子节点的值3交换,在值交换完成后,如果左子节点还有自己的左右子节点的话,就需要对左子节点进行调整,我们可以采用的循环或者递归的方式遍历所有可能受到影响的子节点。建堆时候的调整通常先从最后一个非叶子节点开始调整;
(图片来自博客:http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html)
2. 建立好堆后,就可以开始排序了,过程是先将根节点的值和最有一个叶子节点的值进行交换,相当于把我们需要的值提出来,这时候最有一个叶子节点存放的是整个堆中的最大值和最小值了。虽然我们已经找到了一个已经排序好的值,但是根节点值改变了,就需要重新调整堆了,但是此时我们已经不需要最有一个叶子节点参与调整了,因为它是我们已经排序好的值了,所以我们需要调整成堆的数据元素就会少一个。依次内推,直到提取出所有的值;
具体实现:
1 void adjustHeap(int heap[], int size, int index) 2 { 3 int leftIndex = 2 * index + 1; 4 int rightIndex = 2 * index + 2; 5 int largestIndex = index; 6 7 do { 8 int leftChild = heap[leftIndex]; 9 int rightChild = heap[rightIndex]; 10 11 // 大根堆 12 if (leftIndex < size && leftChild > heap[largestIndex]) { 13 largestIndex = leftIndex; 14 } 15 if (rightIndex < size && rightChild > heap[largestIndex]) { 16 largestIndex = rightIndex; 17 } 18 19 // 需要交换 20 if (largestIndex != index) { 21 int tmp = heap[index]; 22 heap[index] = heap[largestIndex]; 23 heap[largestIndex] = tmp; 24 25 index = largestIndex; 26 27 // 调整子节点的堆 28 leftIndex = 2 * largestIndex + 1; 29 rightIndex = 2 * largestIndex + 2; 30 } 31 else { 32 break; 33 } 34 } while(leftIndex < size && rightIndex < size); 35 } 36 37 void buildHeap(int heap[], int size) 38 { 39 // 从最后的非叶子节点开始 40 for (int i = size / 2 - 1; i >= 0; i--) { 41 adjustHeap(heap, size, i); 42 } 43 } 44 45 void sortHeap(int heap[], int size) 46 { 47 // 建堆 48 buildHeap(heap, size); 49 50 int adjustSize = size; 51 int index = size-1; 52 while (index > 0) { 53 int tmp = heap[0]; 54 heap[0] = heap[index]; 55 heap[index] = tmp; 56 57 adjustSize -= 1; 58 index -= 1; 59 60 // 重新调整堆 61 adjustHeap(heap, adjustSize, 0); 62 } 63 64 printf("_________________________ "); 65 for (int i = 0; i<size; i++) { 66 printf("index: %d value: %d ", i, heap[i]); 67 } 68 }
int A[] = {1,12,7,5,3,89,32,2,76,389,34,9};
sortHeap(A, 12);
时间复杂度:由于堆的一次遍历最糟糕的情况就是树的高度o(logn),而堆排序的主要消耗来自循环调整堆,即时间复杂度为o(nlogn)。