排序算法之堆排序

这里是传送门⇒总结:关于排序算法



平均时间复杂度 最优时间复杂度 最差时间复杂度 空间复杂度 稳定性
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定


堆排序是一种利用“堆”数据结构而设计的排序算法。在升序序列中,利用的是“最大堆”,“最大堆”是“一棵任一父结点均大于或等于其子结点的完全二叉树”。

  • 算法描述
    • 将待排序列初始化为一个“最大堆”,那么由“最大堆”的性质,现在的堆顶元素是待排序列中的最大值
    • 把堆顶元素跟最后一个元素交换,堆长度-1
    • 将堆中剩下的元素重新调整为“最大堆”
    • 重复以上操作...
    • 那么堆排序的过程其实就是不断调整堆的过程,每次调整成堆后都会得到未排序区间中的最大值
  • 调整堆的具体实现
    • 需要了解结点上浮(shiftUp)、结点下沉(shiftDown)
    • 这里需要用到的是结点下沉
    • 由于“最大堆”的性质是“任一父结点均大于或等于其子结点”
    • 需要对所有的父结点进行检查,看是否满足性质,不满足则调整
    • 检查顺序是从最后一个父结点开始,到根结点结束
    • 顺序如此的原因是:若从根结点开始检查,那么根结点的值只能是根结点与其直接后继中的最大值,而不是整个堆的最大值;而若是从最后一个父结点开始检查,就可以把该子树的最大值放在该子树的根结点(即最后一个父结点)上,供上层父结点比较,就能把更大的值放在更上层
    • 结点上浮的实现与下沉类似,这里仅给出代码,实际上在升序的堆排序算法中不需用到
  • JS实现
// 结点上浮:在插入二叉树的最后一个位置时用到,此处没有用到
// 此处的i是完全二叉树里的顺序,与数组里的顺序相比大了1
function MaxHeapShiftUp(array, i) {
    while (i / 2 >= 1) {
        var index = i - 1;
        var pIndex = Math.floor(i / 2) - 1;
        if (array[index] > array[pIndex]) {
            Swap(array, index, pIndex);
        }
        i = pIndex + 1;
    }
}

// 结点下沉
// 此处的i是完全二叉树里的顺序,与数组里的顺序相比大了1
// 此处传入的array会被直接改变
function MaxHeapShiftDown(array, i, len) {
    while (i * 2 <= len) {
        var index = i - 1;
        var sIndex = i * 2 - 1;
        var max = sIndex;
        if (sIndex + 1 < len && array[sIndex] < array[sIndex + 1]) {
            max += 1;
        }
        if (array[index] < array[max]) {
            Swap(array, index, max);
            i = max + 1;
        }else {
            break;
        }
    }
}

// 初始化堆
// 此处传入的array会被直接改变
function BuildMaxHeap(array) {
    var len = array.length;
    for (var i = Math.floor(len / 2); i >= 1; i--) {
        MaxHeapShiftDown(array, i, len);
    }
}

// 此处传入的array会被直接改变
function HeapSort(array) {
    var len = array.length;
    BuildMaxHeap(array);
    for (var i = len - 1; i > 0; i--) {
        Swap(array, 0, i);
        MaxHeapShiftDown(array, 1, --len);
    }
}

为什么建堆的时候(即BuildMaxHeap函数中),最后一个非叶子结点是Math.floor(len / 2)?原因看这个笔记:关于完全二叉树

  • 结点下沉的递归做法
// 这个是递归的做法
// 此处的i是完全二叉树里的顺序,与数组里的顺序相比大了1
function MaxHeapShiftDown_v2(array, i, len) {
    var left, right, max;
    max = left = 2 * i;
    if (left > len) {
        return;
    }
    right = left + 1;
    if (right < len && array[right] > array[left]) {
        max = right;
    }
    if (array[i] < array[max]) {
        Swap(array, i, max);
        MaxHeapShiftDown_v2(array,max,len);
    }
}
  • 分析
    • 上文表中堆排序的复杂度数据是非递归做法的复杂度
    • 执行一次MaxHeapShiftDown函数的时间复杂度为O(logn)
    • 考虑需要时间最长的情况,即对根结点进行下沉,那么执行比较的次数不会超过完全二叉树的深度:[log2n]向下取整 + 1,(深度怎么算的看这个笔记:关于完全二叉树)其中n为结点个数,即时间复杂度为O(logn)
    • 建堆的时间复杂度为O(n)(可以看这个别人家的博客
    • 而堆排序的时间 = 建堆的时间 + 调整堆的时间 = O(n) + nO(logn),即堆排序的时间复杂度为 O(nlogn)
    • 该排序属于原地排序,空间复杂度S(n) = O(1)
posted @ 2021-02-23 22:10  有机物与鱼  阅读(165)  评论(0编辑  收藏  举报