堆排序(Heap Sort)

标签

非稳定排序、原地排序、比较排序

基本思想

直接选择排序中,第一次选择经过了$n - 1$次比较,只是从排序码序列中选出了一个最小的排序码,而没有保存其他中间比较结果。所以后一趟排序时又要重复许多比较操作,降低了效率。J. Willioms和Floyd在1964年提出了堆排序方法,避免这一缺点。

堆排序(Heapsort)是指利用这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆的性质

(1)性质:完全二叉树或者是近似完全二叉树; 

(2)分类:大顶堆:父节点不小于子节点键值,小顶堆:父节点不大于子节点键值;

(3)左右孩子:没有大小的顺序。

(4)堆的存储 

一般都用数组来存储堆,i结点的父结点下标就为$(i – 1) / 2$。它的左右子结点下标分别为$2 ∗ i + 1$和$2 ∗ i + 2$。如第0个结点左右子结点下标分别为1和2。 

(5)堆的操作

  • 建立: 以最小堆为例,如果以数组存储元素时,一个数组具有对应的树表示形式,但树并不满足堆的条件,需要重新排列元素,可以建立“堆化”的树。
  • 插入: 将一个新元素插入到表尾,即数组末尾时,如果新构成的二叉树不满足堆的性质,需要重新排列元素,进行堆的调整。
  • 删除: 堆排序中,删除一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。表中最后一个元素用来填补空缺位置,之后树被更新以满足堆条件。

算法描述

  • 步骤1:将初始待排序关键字序列$(R_1, R_2, …, R_n)$构建成大顶堆,此堆为初始的无序区;
  • 步骤2:将堆顶元素$R[1]$与最后一个元素$R[n]$交换,此时得到新的无序区$(R_1, R_2, …, R_{n-1})$和新的有序区$(Rn)$,且满足$R[1,2…n-1] <= R[n]$;
  • 步骤3:由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区$(R1,R2, …, Rn-1)$调整为新堆,然后再次将$R[1]$与无序区最后一个元素交换,得到新的无序区$(R_1, R_2, …, R_{n-2})和新的有序区(R_{n-1},R_n)。不断重复此过程直到有序区的元素个数为$n - 1$,则整个排序过程完成。

动图演示

时间复杂度

由于每次重新恢复堆的时间复杂度为$O(log n)$,共$n - 1$次堆调整操作,再加上前面建立堆时$n / 2$次向下调整,每次调整时间复杂度也为$O(log n)$。两次次操作时间相加还是$O(nlog n)$。故堆排序的时间复杂度为$O(nlog n)$。

最坏情况:如果待排序数组是有序的,仍然需要$O(nlog n)$复杂度的比较操作,只是少了移动的操作;

最好情况:如果待排序数组是逆序的,不仅需要$O(nlog n)$复杂度的比较操作,而且需要$O(nlog n)$复杂度的交换操作。总的时间复杂度还是$O(nlog n)$。因此,堆排序和快速排序在效率上是差不多的,但是堆排序一般优于快速排序的重要一点是,数据的初始分布情况对堆排序的效率没有大的影响。

平均情况:$O(nlog n)$

空间复杂度

没有额外的空间消耗。

算法示例

线性建堆自下向上调整】

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define swap(a, b) { \
    __type(a) __temp = a; \
    a = b, b = __temp; \
}

void downUpdate(int *arr, int n, int ind) {
    while ((ind << 1) <= n) {
        int temp = ind, l = ind << 1, r = ind << 1 | 1;
        if (arr[l] > arr[temp]) temp = l;
        if (r <= n && arr[r] > arr[temp]) temp = r;
        if (temp == ind) break;
        swap(arr[ind], arr[temp]);
        ind = temp;
    }
    return;
}

void heap_sort(int *arr, int n) {
    arr -= 1; //arr[0]->arr[1]
    for (int i = n >> 1; i >= 1; i--) {
        downUpdate(arr, n, i);
    }
    for (int i = n - 1; i > 1; i--) {
        swap(arr[i], arr[1]);
        downUpdate(arr, n - 1, i - 1);
    }
    return;
}

void output(int *arr, int n) {
    printf("[");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("]\n");
    return;
}

int main() {
    srand(time(0));
    #define MAX_N 20
    int *arr = (int *)malloc(sizeof(int) * (MAX_N + 1));
    for (int i = 0; i < MAX_N; i++) {
        arr[i] = rand() % 100;
    }
    output(arr, MAX_N);
    head_sort(arr, MAX_N);
    output(arr, MAX_N);
    #undef MAX_N
    return 0;
}

参考资料:

https://blog.csdn.net/coolwriter/article/details/78732728

https://blog.csdn.net/weixin_41190227/article/details/86600821

https://www.cnblogs.com/itsharehome/p/11058010.html

posted @ 2020-11-14 16:28  箐茗  阅读(130)  评论(0编辑  收藏  举报