图说堆排序

作者: 字节武装 | 来源:发表于2020-03-26 10:42 被阅读0次

    这是【字节可视化系列】堆排序的第一篇文章。同时,文末会放上一张【速记卡】,方便快速回顾本文关键知识点。

    本篇的主旨是理解二叉堆结构,所以具体实现代码会留到第二篇讲解。

    提到堆,其实Java中的优先队列PriorityQueue就是基于堆结构实现的;

    而Java中的延迟队列DelayQueue里面就用到了PriorityQueue;

    再比如kafka中基于时间轮TimingWheel实现的延时定时器, 同样离不开DelayQueue的配合。

    这一切都和堆息息相关。

    1 数组和树

    堆其实就是一个数组结构, 如图

    下面我们来点小魔法, 把数组变成树!

    这个数组对应的树结构就是下面这样:

    数组结构的下标和树结构的节点对应关系如下:

    如果根节点对应的数组下标是0, 那么对于 i 节点,

    左右子节点的位置为 2i +1和 2i+2;

    i 节点的父节点位置为 ( i-1)/2;

    简单理解了数组和树之后,下面我们进入正题。

    2 最小堆结构

    首先我们来一个乱序的数组:

    这个数组对应的树结构:

    这只是一个简单的树结构, 还不是堆, 堆结构是下面这样子的:

    这是一个最小堆, 可以看到根节点, 也就是堆顶, 是数组中最小的那个元素。

    同时,整一个大的堆, 是由很多个小的堆组成的。

    比如左边这个小堆, 也是符合最小堆的定义, 堆顶的4是三个数字中最小的:

    右边的小堆也是一个最小堆, 堆顶的3是最小的数字:

    那么怎么把一颗树变成一个最小二叉堆呢?

    3 插入和上浮

    最简单的办法, 就是新建一个空的堆(一个空的数组), 把乱序数组中的所有元素依次插入到堆中, 在这个过程中堆会通过上浮来调整自身的结构。

    下面我们来解析下上面的动画:

    首先, 是往堆中 插入 元素, 比如往下面的这个最小堆中插入数字1:

    当1插入数组尾部之后, 就处于树的最后一个叶子节点的位置:

    然后通过 上浮 操作, 把A移到合适的位置。

    简单来说就是先和A的父节点比较, 如果比父节点小, 就和父节点交换位置, 这个过程一直持续到A的父节点比A小, 或者A已经到达堆顶的位置。

    下面继续数字1这个例子, 当1插入数组尾部之后, 会和他的父节点8作比较:

    1比父节点8小, 交换位置:

    1再和他的父节点4作比较:

    1比父节点4小, 交换位置后1到达堆顶, 形成了一个最小堆结构。

    4 删除和下沉

    接下来讲讲堆节点的删除, 这里的删除是指取出堆顶的节点。

    我们把堆顶的元素取出后, 堆会把剩下的数字中最小的那个数字再次推到堆顶的位置。

    仔细看动画,这里值得注意的是, 取出堆顶的元素, 并不是直接把堆顶的元素删除。什么意思呢?

    在JavaScript中, 数组的pop操作就是把数组中最后一个元素删除; 而Java中的堆栈Stack也有pop操作, 同样是把最后一个元素移除。

    而堆的 删除 操作, 也是基于pop操作来实现。这样做是为了维持完全二叉树的结构。

    比如我们要把堆顶的1删除:

    1. 先把根节点和最后一个节点交换位置, 也就是1和6交换位置;

    此时堆顶不再是最小的数字,最小堆的特性被打破, 接下来我们要对堆顶的元素进行 下沉 操作, 把树结构重新调整为堆结构。

    2. 先比较左右子节点大小,与最小节点交换位置 ;

    3. 继续步骤2;

    继续上面的例子,当1节点移除后,此时会先对堆顶6的左右子节点进行大小比较, 找出比较小的那个子节点,也就是右节点3:

    然后, 节点6再和右节点3比较:

    节点6下沉, 此时6已经是叶子节点了, 下沉操作结束, 可以看到此时整棵树已经重新变回了最小堆结构:

    5 堆排序

    有趣的是, 通过上面简单的【插入并上浮】 和 【删除并下沉】 , 我们就可以对乱序数组执行堆排序了。

    下面对乱序数组进行堆排序:

    堆化,插入并上浮:

    删除并下沉:

    排序完成后的数组:

    最后对堆排序的总结就是:

    1. 先把乱序数组堆化 (文中使用的是插入+上浮)

    2. 再通过pop操作删除堆顶元素, 通过下沉调整堆结构

    6 堆化 Heapify

    平常的使用堆排序肯定不会再新建一个空的堆, 再把乱序数组的元素逐个插入堆中, 这样太浪费空间, 下一章我们来讲一下堆化。

    7 色谱图

    简单了解了堆排序后, 我们来看看16个元素进行堆排序后生成的色谱图

    也有网友说这是大肠图, 感兴趣的可以看看这篇文章了解下【译】排序算法可视化之色谱图, 后面本公众号会开一个系列专门讲解这种有趣的静态可视化方法。

    我们把这个图拆成两部分来看, 首先是左边部分

    这就是堆排序的第一步, 堆乱序数组堆化的过程

    再来看看右边部分

    可以看到, 堆排序的第二步就是pop操作把堆顶元素移除, 图中也体现得非常清晰,先把堆顶元素移到尾部, 被移除的元素就相当于排好序了.

    每次移除元素后, 都会对新的堆顶节点执行下沉操作, 也就是图中pop和pop之间的红框部分, 我们可以清晰地看到堆在自我调节的过程

    排序结束之后, 由浅到深的颜色依次呈现在我们眼前!

    温馨提示

    文中的动画是基于d3.js制作而成。

    相关文章

      网友评论

        本文标题:图说堆排序

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