算法熟记-排序系列-堆排序
1. 简述
假设待排序数组为 int array[], 数组长度为n。
主要是利用堆的性质。对于升序排序,使用最大堆。
首先,建堆,使用递归后根序遍历得方法,通过交换元素,保证根元素比孩子元素大。
第1趟,堆顶元素array[0]与array[n-1]交换,保证array[n-1]的数值正确,根据array[0]新的数值更新堆。
第2趟,堆顶元素array[0]与array[n-2]交换,保证array[n-2]的数值正确,根据array[0]新的数值更新堆。
···
第n-1趟,堆顶元素array[0]与array[1]交换,保证array[1]的数值正确,根据array[0]新的数值更新堆。
2. 复杂度
平均时间复杂度为O(N*logN),空间复杂度为O(1)。
3. 代码
void make_heap(int array[], int n, int node) { // 自底向上,构建堆
int left = 2 * node + 1;
int right = 2 * node + 2;
if(left > n-1) return;
else if(right > n-1) { // 堆是完全的二叉树,所以此时不需要递归
if(array[node] < array[left]) {
swap(array[node], array[left]);
}
}
else {
make_heap(array, n, left);
make_heap(array, n, right);
if(array[node] < array[left] && array[right] <= array[left]) {
swap(array[node], array[left]);
}
else if(array[node] < array[right] && array[left] <= array[right]) {
swap(array[node], array[right]);
}
}
}
void update_heap(int array[], int n, int node) { // 自顶向下,更新堆
int left = 2 * n + 1;
int right = 2 * n + 2;
if(left > n-1) return;
else if(right > n-1) {
if(array[node] < array[left])
swap(array[node], array[left]);
}
else {
if(array[node] < array[left] && array[right] <= array[left]) {
swap(array[node], array[left]);
update_heap(array, n, left);
}
else if(array[node] < array[right] && array[left] <= array[right]) {
swap(array[node], array[right]);
update_heap(array, n, right);
}
}
}
void heap_sort(int array[], int n) {
make_heap(array, n, 0);
for(int i=n; i>=1; i--) {
swap(array[i], array[0]);
update_heap(array, n, node);
}
}
int left = 2 * node + 1;
int right = 2 * node + 2;
if(left > n-1) return;
else if(right > n-1) { // 堆是完全的二叉树,所以此时不需要递归
if(array[node] < array[left]) {
swap(array[node], array[left]);
}
}
else {
make_heap(array, n, left);
make_heap(array, n, right);
if(array[node] < array[left] && array[right] <= array[left]) {
swap(array[node], array[left]);
}
else if(array[node] < array[right] && array[left] <= array[right]) {
swap(array[node], array[right]);
}
}
}
void update_heap(int array[], int n, int node) { // 自顶向下,更新堆
int left = 2 * n + 1;
int right = 2 * n + 2;
if(left > n-1) return;
else if(right > n-1) {
if(array[node] < array[left])
swap(array[node], array[left]);
}
else {
if(array[node] < array[left] && array[right] <= array[left]) {
swap(array[node], array[left]);
update_heap(array, n, left);
}
else if(array[node] < array[right] && array[left] <= array[right]) {
swap(array[node], array[right]);
update_heap(array, n, right);
}
}
}
void heap_sort(int array[], int n) {
make_heap(array, n, 0);
for(int i=n; i>=1; i--) {
swap(array[i], array[0]);
update_heap(array, n, node);
}
}
实际上,堆的构建和更新都可以使用非递归的方式实现,对于堆的构建,需要首先找到最后一个有孩子的节点array[k],然后从array[k]一直更新到array[0]即可,其中的k=n/2。k的求法如下:假设k存在,2*k+1=n或者2*k+2=n,对于第一种情况,k==n/2,对于第二种情况,k==n/2-1。对于堆的更新,就更简单了,只要从array[0]开始,选择一条通路,一直向下更新,直到没有孩子了为止。
值得注意的是,对于下标从0开始的数组,k号节点的孩子节点分别是2*k+1和2*k+2。 而对于下标从1开始得数组,k号节点的孩子节点分别是2*k和2*k+1。
堆排序属于选择排序,实际上就是利用最大堆这个数据结构,每次选择一个剩余元素中最大的元素,交换到合适的位置上去。
4. 参考资料