堆排序(heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆与小根堆。是完全二叉树。
满二叉树:除了叶子节点之外的每一个节点都有两个孩子,并且所有的叶子都在同一层上,这样的二叉树称为满二叉树。
1.叶子节点只能出现在最下一层,出现在其他层就不可能达成平衡。2.非叶子节点的度一定是2(度指拥有的子树数目)3.在同样深度的二叉树中,满二叉树的节点个数最多,叶子数最多。完全二叉树:除了最后一层之外的其他每一层节点个数都达到最大个数,并且一层节点都保持向左对齐,这就是完全二叉树。
1.叶子节点只能出现在最下层和次下层2.最下层的叶子节点集中在树的左部3.如果节点的度为1,则该节点只有左孩子,即没有右子树。满二叉树一定是完全二叉树,反过来不成立。
简单来说,堆排序就是将数据看成完全二叉树、根据完全二叉树的特性来进行排序的一种算法。
最大堆要求节点的元素都要不小于其孩子,最小堆要求节点的元素都不大于其孩子。那么处于最大堆的根节点的元素一定是这个堆中的最大值。那么,完全二叉树与堆有什么关系呢?
我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递推,就是根节点的值最小,这样的树,称为小根堆。
同理,又有一棵完全二叉树,对于任意一个子节点来说,均不大于其父节点的值,如此递推,就是根节点的值是最大的,这样的数,称为大根堆。
如上图,左边就是大根堆;右边则是小根堆,这里必须要注意一点,只要求子节点与父节点的关系,两个节点的大小关系与其左右位置没有任何关系。
明确下大根堆,小根堆的概念,继续说堆排序。
现在对于堆排序来说,我们先要做的是,把待排序的一堆无序的数,整理成一个大根堆,或者小根堆,下面讨论以大根堆为例子。
给定一个列表array=[16,7,3,20,17,8],对其进行堆排序(使用大根堆)。
接下来内容是转载部分,自己绘图功底太差:其中绿色部分为自己的注解。
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
a.假设给定无序序列结构如下
2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
此处必须注意,我们把6和9比较交换之后,必须考量9这个节点对于其子节点会不会产生任何影响?因为其是叶子节点,所以不加考虑;但是,一定要熟练这种思维,写代码的时候就比较容易理解为什么会出现一次非常重要的交换了。
4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
在真正代码的实现中,这时候4和9交换过后,必须考虑9所在的这个节点位置,因为其上的值变了,必须判断对其的两个子节点是否造成了影响,这么说不合适,实际上就是判断其作为根节点的那棵子树,是否还满足大根堆的原则,每一次交换,都必须要循环把子树部分判别清楚。
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
牢记上面说的规则,每次交换都要把改变了的那个节点所在的树重新判定一下,这里就用上了,4和9交换了,变动了的那棵子树就必须重新调整,一直调整到符合大根堆的规则为截。
此时,我们就将一个无序序列构造成了一个大顶堆。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
a.将堆顶元素9和末尾元素4进行交换
这里,必须说明一下,所谓的交换,实际上就是把最大值从树里面拿掉了,剩下参与到排序的树,其实只有总结点的个数减去拿掉的节点个数了。所以图中用的是虚线。
b.重新调整结构,使其继续满足堆定义
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
下面,附上我的代码。
堆排序的最好、最坏、平均时间复杂度都是O(nlogn),空间复杂度为O(1),是不稳定的排序算法
运行时间主要消耗在构造堆和重建堆时的反复筛选上。
构造堆的时间复杂度为O(n) 重建堆时时间复杂度为O(logn)。
所以总体就是O(nlogn)。 不适合排序序列个数较少的情况。