• 堆排序(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是清明的茗
  • 相关阅读:
    作业3月30号
    21、,模块与包的使用
    作业3月26号
    20、面向函数与匿名函数及模块
    作业3月25号
    19、迭代器及函数的递归调用
    作业3月24号
    06-函数
    3.17---购物车练习
    3.15---文件处理练习2
  • 原文地址:https://www.cnblogs.com/MinPage/p/13963644.html
Copyright © 2020-2023  润新知