堆排序是原地排序、时间复杂度O(nlogn)、不稳定

1.性质:
  • 堆是一个完全二叉树
  • 任何节点的值都大于等于(或小于等于)子树节点的值
2.特点

每个节点的值都大于等于(或小于等于)其子树节点的值。因此,堆被分成了两类,大顶堆和小顶堆。

3.大顶堆的操作

1.插入一个元素
把新插入的数据放到数组的最后从下往上的堆化方法让新插入的节点与父节点对比大小。如果不满足子节点小于等于父节点的大小关系,我们就互换两个节点。一直重复这个过程,
堆和堆排序
2. 删除堆顶元素
把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。这就是从上往下的堆化方法
堆和堆排序
往堆中插入一个元素和删除堆顶元素的时间复杂度都是 O(logn)。

4.堆排序

  1. 建堆
    我们首先将数组原地建成一个堆,不借助另一个数组,就在原数组上操作。建堆的过程,有两种思路:
    1). 在堆中插入一个元素的思路。数组中包含 n 个数据,假设堆中只有一个数据(下标为1),然后调用插入操作,将下标从2-n的数据依次插入到堆中。
    2). 从后向前处理数组,每个数据都从下往上堆化

建堆过程的时间复杂度是 O(n),推导:
堆和堆排序
将每个非叶子节点的高度求和,就是下面这个公式:
堆和堆排序
把公式左右都乘以 2,就得到另一个公式 S2。我们将 S2 错位对齐,并且用 S2 减去 S1,可以得到 S
堆和堆排序
堆和堆排序
因为 h=log2​n,代入公式 S,就能得到 S=O(n),所以,建堆的时间复杂度就是 O(n)

  1. 排序
    大顶堆,第一个元素是最大元素,把它和最后一个元素交换,那最大元素就放到下标n的位置,然后通过堆化方法,把剩下n-1个元素重新构建成堆,堆化完成之后,再取堆顶元素,放到n-1的位置,重复过程,知道堆中剩下1个元素。堆和堆排序

5. 问题

1、在实际开发中,为什么快速排序要比堆排序性能好?

  1. 第一点:堆排序数据访问方式没有快速排序友好
    快排,数据是顺序访问的;堆排,数据是跳着访问的。堆化,不像快排局部顺序访问,所以,对cpu缓冲不友好
  2. 第二点:对于同样的数据,堆排序交换的次数比较多
    对于基于比较排序算法来说,比较和交换,快排数据交换不会多余逆序度
    而堆排序,第一步得建堆,如果一组有序的数据,建堆之后也会变得无序。

2.对于完全二叉树来说,下标从 2n​+1 到 n 的都是叶子节点,这个结论是怎么推导出来的呢?

  1. 反证法:如果下标为n/2 + 1的节点不是叶子节点,即它存在子节点。它的左子节点为:2(n/2 + 1) = n + 2,这个数字已经大于n + 1,超出了实现完全二叉树所用数组的大小(数组下标从1开始记录数据,对于n个节点来说,数组大小是n + 1),左子节点都已经超出了数组容量,更何况右子节点。以此类推,很容易得出:下标大于n/2 + 1的节点肯定都是也叶子节点了,故而得出结论:对于完全二叉树来说,下标从n/2 + 1 到 n的节点都是叶子节点

3.堆的应用除了堆排以外,还有如下一些应用:

  1. 从大数量级数据中筛选出top n 条数据; 比如:从几十亿条订单日志中筛选出金额靠前的1000条数据
  2. 在一些场景中,会根据不同优先级来处理网络请求,此时也可以用到优先队列(用堆实现的数据结构);比如:网络框架Volley就用了Java中PriorityBlockingQueue,当然它是线程安全的
  3. 可以用堆来实现多路归并,从而实现有序,leetcode上也有相关的一题:Merge K Sorted Lists

相关文章: