美文网首页
堆排序---基础篇

堆排序---基础篇

作者: 张虾米试错 | 来源:发表于2019-02-24 21:56 被阅读0次

本文主要介绍堆排序的一些基本过程和分析。

大纲

  1. 堆排序简介
  2. 堆排序代码实现

1. 堆排序简介

1.1 堆排序的存储

一般用数组表示堆,若根结点存在序号0处, i结点的父结点下标就为(i-1)/2i结点的左右子结点下标分别为2*i+12*i+2。(注:如果根结点是从1开始,则左右孩子结点分别是2i2i+1。)

1.2 堆排序的过程

想要清楚堆排序的过程,首先需要明白两个问题:

  • 如何由一个无序序列建成一个堆?
  • 如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
如何由一个无序序列建成一个堆?

用数组存储序列,那么初始状态如下图:


初始状态

接下来我们需要对初始状态做调整,假设我们创建最小堆,那么从最后一个非叶子节点开始进行调整,使得以该节点为根节点的子树满足最小堆的要求;直到调整到根节点。

由于节点i的左右节点分别为2i+12i+2,最开始从倒数的第一个非叶子节点开始调整,因此该节点为len(array)/2 - 1;直到调整到节点0,完成最开始的调整。

如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?

一般在输出堆顶元素之后,视为将这个元素排除,然后用表中的最后一个元素填补它的位置,自下向上调整:首先,将堆顶元素和它的左右子树的根节点进行比较,把最小的元素交换到堆顶;然后,顺着被破坏的路径一路调整下去,直至叶子节点,就得到新的堆。我们称这个自堆顶至叶子的调整过程为“筛选”。重复这个过程,直到完成排序;该过程的每一次调整都是从根节点开始调整。

1.3 复杂度分析

堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。因此,堆排序的时间复杂度分两个部分。

初始化建堆是从第一个非叶子节点开始的,因此所有的叶子节点是不需要调整的,而需调整节点是与它们的子节点以及子树相比较。那么每一层的时间复杂度为s_i = 2^{( i - 1 )} * ( k - i ),其中2^{( i - 1 )}表示第i层上的节点个数,k表示树的深度,k-i表示要比较的次数。

所以,总的时间复杂度为:

S = 2^{(k-2)} * 1 + 2^{(k-3)}*2.....+2*(k-2)+2^{(0)}*(k-1)

等式的求解,等式左右乘上2,然后和原来的等式相减,可以得到:

S = 2^{(k - 1)} + 2^{(k - 2)} + 2^{(k - 3)} ..... + 2 - (k-1)
根据等比数列的求和公式:

S = \frac{a_1[ 1- (q^n) ] }{(1-q)}

最终得到:

s = 2^k -k -1
因为堆排序为完全二叉树,因此k = log(n)。所以s = n -logn-1,初始化建堆的时间复杂度就是O(n).

重新调整的过程是顶点节点与其子树调整,循环n-1次,每次调整的次数最多logn,因此该部分的事件复杂度为O(nlogn).

综上分析,堆排序的时间复杂度是O(nlogn)。而且,堆排序在最坏的情况下,其时间复杂度也为O(nlogn)。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。

2. 堆排序代码实现

def sift_down(arr, start, end):
    root = start
    while True:
        # 从root开始对最大堆调整
        child = 2 * root + 1
        if child > end:
            break

        # 找出两个child中交大的一个
        if child + 1 <= end and arr[child] < arr[child + 1]:
            child += 1

        if arr[root] < arr[child]:
            # 最大堆小于较大的child, 交换顺序
            arr[root], arr[child] = arr[child], arr[root]

            # 正在调整的节点设置为root
            root = child
        else:
            # 无需调整的时候, 退出
            break


def heap_sort(arr):
    # 从最后一个有子节点的孩子开始调整最大堆
    # 每次调整以该节点为start节点,也就是说调整的是该节点的子树。
    first = len(arr) // 2 - 1
    for start in range(first, -1, -1):
        sift_down(arr, start, len(arr) - 1)

    # 将最大的放到堆的最后一个, 堆-1, 继续调整排序
    for end in range(len(arr) -1, 0, -1):
        arr[0], arr[end] = arr[end], arr[0]
        sift_down(arr, 0, end - 1)

def main():
    # [7, 95, 73, 65, 60, 77, 28, 62, 43]
    # [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    l = [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
    print l
    heap_sort(l)
    print l


if __name__ == "__main__":
    main()

参考资料

相关文章

  • 堆排序---基础篇

    本文主要介绍堆排序的一些基本过程和分析。 大纲 堆排序简介 堆排序代码实现 1. 堆排序简介 1.1 堆排序的存储...

  • C++基础入门之模板堆排序(下):堆排序——简单的翻译

    上一篇C++基础入门之模板堆排序(上):模板上的list的创造与操作 其实接下来就很简单了,只要把大众版的堆排序翻...

  • 算法

    现在写了6篇排序算法,正在写堆排序,我发现真的是,都是以递归为基础的,感觉非常神奇,但是写着写着就感觉很迷茫,感觉...

  • 算法基础--堆排序

    本文只是自己的笔记,并不具备任何指导意义。为了理解很多都使用了递归,而不是自己进行压栈处理。代码的初衷是便于理解,...

  • 基础算法|堆排序

    堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满...

  • 二叉树-js(2.堆排序)

    堆排序 参考:堆排序中建堆过程时间复杂度O(n)怎么来的? 二叉树-js(1.基础知识与基本操作):中介绍了数组如...

  • 堆排序

    目录 1.堆排序介绍 2.堆排序图文说明 3.堆排序的时间复杂度和稳定性 4.堆排序实现 堆排序介绍 堆排序(He...

  • 开发者应该掌握的几种排序算法

    该篇文章主要介绍了算法基础以及几种常见的排序算法:选择排序、插入排序、冒泡排序、快速排序、堆排序。 一、算法基础 ...

  • 数据结构与算法-排序/二分查找

    算法中基础中的基础,排序/二分查找 排序 1.快排QuickSort 归并排序 堆排序 1. 二分查找

  • 堆和堆排序

    最小K个数 堆排序 堆排序

网友评论

      本文标题:堆排序---基础篇

      本文链接:https://www.haomeiwen.com/subject/pqfiyqtx.html