堆排序算法
桶排序算法
算法简介
堆是一个完全二叉树。
完全二叉树: 二叉树除开最后一层,其他层结点数都达到最大,最后一层的所有结点都集中在左边(左边结点排列满的情况下,右边才能缺失结点)。
大顶堆:根结点为最大值,每个结点的值大于或等于其孩子结点的值。
小顶堆:根结点为最小值,每个结点的值小于或等于其孩子结点的值。
堆的存储: 堆由数组来实现,相当于对二叉树做层序遍历。如下图:
对于结点 i ,其子结点为 2i+1 与 2i+2 。
算法描述
现在需要对如上二叉树做升序排序,总共分为三步:
将初始二叉树转化为大顶堆(heapify)(实质是从第一个非叶子结点开始,从下至上,从右至左,对每一个非叶子结点做shiftDown操作),此时根结点为最大值,将其与最后一个结点交换。
除开最后一个结点,将其余节点组成的新堆转化为大顶堆(实质上是对根节点做shiftDown操作),此时根结点为次最大值,将其与最后一个结点交换。
重复步骤2,直到堆中元素个数为1(或其对应数组的长度为1),排序完成。
下面详细图解这个过程:
步骤1:
初始化大顶堆,首先选取最后一个非叶子结点(我们只需要调整父节点和孩子节点之间的大小关系,叶子结点之间的大小关系无需调整)。设数组为arr,则第一个非叶子结点的下标为:i = Math.floor(arr.length/2 - 1) = 1,也就是数字4,如图中虚线框,找到三个数字的最大值,与父节点交换。
然后,下标 i 依次减1(即从第一个非叶子结点开始,从右至左,从下至上遍历所有非叶子节点)。后面的每一次调整都是如此:找到父子结点中的最大值,做交换。
这一步中数字6、1交换后,数字[1,5,4]组成的堆顺序不对,需要执行一步调整。因此需要注意,每一次对一个非叶子结点做调整后,都要观察是否会影响子堆顺序!
这次调整后,根节点为最大值,形成了一个大顶堆,将根节点与最后一个结点交换。
步骤2:
除开当前最后一个结点6(即最大值),将其余结点[4,5,3,1]组成新堆转化为大顶堆(注意观察,此时根节点以外的其他结点,都满足大顶堆的特征,所以可以从根节点4开始调整,即找到4应该处于的位置即可)。
步骤3:
接下来反复执行步骤2,直到堆中元素个数为1:
堆中元素个数为1, 排序完成。
代码示例
1 | // 交换两个节点 |
程序注释:
将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面做第一次堆化时,heapSort 中写了一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点都执行 shiftDown操作,所以就满足了每一次 shiftDown中,结点 i 以下的子堆已经是一大顶堆。
复杂度
建立堆的时间复杂度为0(n),排序循环的次数为n-1,每次调整堆的时间复杂度为0(logn) 因此堆排序的时间复杂度不管在什么情况下都是0(logn)