1.堆定义
堆的性质:
[1]堆中某个节点的值总是不大于或不小于其父节点的值;
[2]堆总是一棵完全二叉树。
[3]将根节点最大的堆叫做大根堆,根节点最小的堆叫做小根堆。
堆还是很简单的,父节点的值总是大于(或者总是小于)子节点的值,但是左右子节点没有大小顺序。由于是完全二叉树,所以可以用数组表示堆。
2.堆排序
以下是从小到大排序的步骤:
[1]把一个无序数组构造成一个大顶堆。(完成后根节点最大,但是一个节点的左右子节点是无序的)
[2]将堆顶元素和末尾元素交换,然后调整剩下的元素使其继续成为一个大顶堆。(此时最后一个元素是最大的值,堆顶元素是除最后一个元素外最大的值(次大值),因此此时把倒数第二个元素作为末尾元素)
[3]重复第2步。
总结就是,第一步把数组构造成大顶堆后,此时根节点值最大。第二步,根和末尾交换,此时末尾元素最大,然后把除末尾的剩下元素继续调整成一个大根堆,此时根是次大元素。最后再重复第二步就可以了(因为最后的元素已排序好,所以重复时,末尾是前面未排序的末尾)。
另外如果是从大到小排序,则上面只要把大根堆改为构造小根堆就可以。
3.堆排序代码
#include <stdio.h> void swap(int *array, int a, int b) { int temp; temp = array[a]; array[a] = array[b]; array[b] = temp; return ; } static void print_array(int *array, int len) { int i; for(i=0; i<len; i++){ printf("%d ", array[i]); } printf("\n"); } void adjust(int *array, int n, int len) { int i; int temp = array[n]; /* 调整下标为n的元素,使n和n的子树成为大根堆。 注意这里有个前提条件是n的子树本身就是大根堆,因为第一步构造时是从第一个非叶子节点开始调整的,所以会一直满足这个条件。 流程就是,比较n的两个叶子节点,把其中大的和n交换(如果大于n的值),例如和右子节点交换,由于交换后n的右子树可能不再满足大跟堆,所以继续向下处理。 */ for(i=n*2 + 1; i < len; i = i*2 + 1){ if(i+1 < len && array[i] < array[i + 1]) i++; if(array[i] > temp){ array[n] = array[i]; n = i; }else break; } array[n] = temp; } //从小到大排序 void heap_sort(int *array, int len) { int i; // 第一步,构造大顶堆 for(i=len/2 - 1; i>=0; i--){ adjust(array, i, len); } for(i=len-1; i>0; i--){ swap(array, 0, i); //根和末尾交换 adjust(array, 0, i); //然后把剩下的元素再调成大根堆,由于上面交换后,i个元素中,只有堆顶的元素不满足大根堆,所以传入的参数是0,也就是要调整堆顶元素 } } int main() { int i; int array[]={5,3,7,6,8,9}; heap_sort(array, sizeof(array)/sizeof(int)); print_array(array, sizeof(array)/sizeof(int)); return 0; }
[1]代码中,和上面说明的步骤是对应的。唯一注意的就是adjust()函数流程:传入的参数n,就是要把n和n的子树调成一个大跟堆,并且要保证调用该函数之前,n的子树本来就是一个大跟堆。因为构造大跟堆是从倒数第一个非叶子节点开始的,所以这个条件是一直都会满足的。
[2]继续用图示说明adjust()函数中循环的意义,例如在上面第一步构造大顶堆: