• 经典排序算法学习笔记七——堆排序


    堆排序

     

    数据结构 数组
    最差时间复杂度 O(n*log n)
    最优时间复杂度 O(n*log n)
    平均时间复杂度 O(n*log n)
    最差空间复杂度 О(n) total, O(1) auxiliary

    1、堆的基础知识

    堆节点的访问

    通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:

    • 父节点i的左子节点在位置(2*i+1);
    • 父节点i的右子节点在位置(2*i+2);
    • 子节点i的父节点在位置floor((i-1)/2);

    堆的操作

    在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:

    • 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
    • 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
    • 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

    2、算法思想

    1. 调用创建堆函数将输入数组A[1...n]造成一个最大堆,使得最大的值存放在数组第一个位置A[1]
    2. 然后用数组最后一个位置元素与第一个位置进行交换,并将堆的大小减少1
    3. 调用最大堆调整函数从第一个位置调整最大堆

    3、伪代码

    //from 算法导论
    1
    .下标计算[为与程序对应,下标从0开始] Parent(i)://为了伪代码描述方便 return i/2 Left(i): return 2*i+1 Right(i): return 2*i+2 2.使下标i元素为根的的子树成为最大堆 MAX_HEAPIFY(A,i): l<——Left(i) r<——Right(i) if l<length(A)and A[l]>A[i] thenlargest<——l else largest<——i if r<length(A)and A[r]>A[largest] thenlargest<——r if largest!=i then exchange A[i]<——>A[largest]//到这里完成了一层下降 MAX_HEAPIFY(A,largest)//这里是递归的让当前元素下降到最低位置 3.最大堆的建立,将数组A编译成一个最大堆 BUILD_MAX_HEAP(A): heapsize[A]<——length[A] for i<——length[A]/2+1 to0 MAX_HEAPIFY(A,i)//堆排序的开始首先要构造大顶堆,这里就是对内层节点进行逐级下沉(小元素) 4.堆排序 HEAP_SORT(A): BUILD_MAX_HEAP(A) for i<——length[A]-1to1//这里能够保证堆大小和数组大小的关系,堆在每一次置换后都减一 do exchangeA[1]<——> A[i] length[A]<——length[A]-1 MAX_HEAPIFY(A,0)//对交换后的元素下沉

    4、实现

    #include <stdio.h>
    #include <stdlib.h>
    
    void swap(int* a, int* b) {
        int temp = *b;
        *b = *a;
        *a = temp;
    }
    
    void max_heapify(int arr[], int start, int end) {
        //建立父節點指標和子節點指標
        int dad = start;
        int son = dad * 2 + 1;
        while (son <= end) { //若子節點指標在範圍內才做比較
            if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點大小,選擇最大的
                son++;
            if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
                return;
            else { //否則交換父子內容再繼續子節點和孫節點比較
                swap(&arr[dad], &arr[son]);
                dad = son;
                son = dad * 2 + 1;
            }
        }
    }
    
    void heap_sort(int arr[], int len) {
        int i;
        //初始化,i從最後一個父節點開始調整
        for (i = len / 2 - 1; i >= 0; i--)
            max_heapify(arr, i, len - 1);
        //先將第一個元素和已排好元素前一位做交換,再從新調整,直到排序完畢
        for (i = len - 1; i > 0; i--) {
            swap(&arr[0], &arr[i]);
            max_heapify(arr, 0, i - 1);
        }
    }
    
    int main() {
        int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
        int len = (int) sizeof(arr) / sizeof(*arr);
        heap_sort(arr, len);
        int i;
        for (i = 0; i < len; i++)
            printf("%d ", arr[i]);
        printf("
    ");
        system("pause");
        return 0;
    }

    5、复杂度分析

    1、它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。

    (1)在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)

    (2)在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(n*log n)

    所以总体来说,堆排序的时间复杂度为O(n*logn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(n*logn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。

    2、空间复杂度上,需要一个单元的辅助空间用于交换,所以辅助空间为O(1)。

    不过由于记录的比较与交换是跳跃式进行,因此堆排序是一种不稳定的排序方法——引用自冰河的博客http://blog.csdn.net/u012152619/article/details/47452813#t2

  • 相关阅读:
    描述一下Spring Bean的生命周期
    BeanFactory和ApplicationContext有什么区别
    谈谈你对AOP的理解
    谈谈对IOC的理解
    线程池中线程复用原理
    线程池中阻塞队列的最用?为什么是先添加队列而不是先创建最大线程
    为什么使用线程池?解释下线程池参数
    去噪声论文阅读
    怎么使用有三AI完成系统性学习
    JavaCnn项目注解
  • 原文地址:https://www.cnblogs.com/crystalmoore/p/5931587.html
Copyright © 2020-2023  润新知