zoukankan      html  css  js  c++  java
  • 堆排序(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("]
    ");
        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

    Min是清明的茗
  • 相关阅读:
    SPOJ GSS4 Can you answer these queries IV ——树状数组 并查集
    SPOJ GSS3 Can you answer these queries III ——线段树
    SPOJ GSS2 Can you answer these queries II ——线段树
    SPOJ GSS1 Can you answer these queries I ——线段树
    BZOJ 2178 圆的面积并 ——Simpson积分
    SPOJ CIRU The area of the union of circles ——Simpson积分
    HDU 1724 Ellipse ——Simpson积分
    HDU 1071 The area ——微积分
    HDU 4609 3-idiots ——FFT
    BZOJ 2194 快速傅立叶之二 ——FFT
  • 原文地址:https://www.cnblogs.com/MinPage/p/13963644.html
Copyright © 2011-2022 走看看