常见数据结构堆的误区,3分钟了解堆

什么是堆?

在数据结构中, 堆是一个具有特殊顺序的数组, 不过能近似的看成一个完全二叉树.

假设有一个数组, 长度为6. 该数组下标从1开始.

数组

将数组转换成完全二叉树的形式
完全二叉树
通过上图看出下面非常关键的性质:

  1. 左孩子序号 = 父节点序号 * 2
  2. 右孩子序号 = 父节点序号 * 2 + 1
  3. 父节点序号 = 孩子节点序号 / 2

以上三个性质是非常重要的性质. 后续的编程也会利用到提及的性质.

为什么下标从1开始?
下标从1开始是为了好理解. 在实际编码的时候, 下标是从0开始. 则对应的节点关系如下:

  1. 左孩子序号 = 父节点序号 * 2 + 1, 也等于 ( 父节点序号 + 1 ) * 2 - 1
  2. 右孩子序号 = 父节点序号 * 2 + 2, 也等于 ( 父节点序号 + 1 ) * 2
  3. 父节点序号 = ( 孩子节点序号 - 1 ) / 2

最小堆/最大堆

这里以最小堆举例. 最小堆的性质: 任意一个节点, 都必须小于等于他的子节点. 根据这个性质. 可以举个例子

建立最小堆

假设现在有一个整数数组[2 ,4 ,1 ,9 ,10 ,-1], 现在对该数组建立最小堆. 先用完全二叉树描述建立过程, 然后再用数组描述过程.

完全二叉树过程

  1. 判断序号为3的节点,(思考为什么从序号为3的节点开始?), 判断需要交换. 则生成第二个图.
  2. 判断序号为2的节点, 发现不需要数据交换.
  3. 判断序号为1的节点, 需要发生数据交换
  4. 判断序号为3的节点, 因为序号为3的节点含有子节点, 所以需要判断. 并且判断为需要数据交换.

下面用数组描述具体过程:
数组过程

为什么要从序号为3的节点进行循环?
因为序号为3的节点是最后一个非叶子节点.
假设有N个节点. 则有 ⌊logN⌋ 层. 最后一层可能的序号则在 2 ⌊ l o g 2 N ⌋ − 1 2^{⌊log_2N⌋-1} 2log2N1 ~ 2 ⌊ l o g 2 N ⌋ 2^{⌊log_2N⌋} 2log2N. 所以从要从 2 ⌊ l o g 2 N ⌋ − 1 − 1 2^{⌊log_2N⌋-1}-1 2log2N11 序号的节点遍历, 对 2 ⌊ l o g 2 N ⌋ − 1 − 1 2^{⌊log_2N⌋-1}-1 2log2N11 简化等于 N / 2 − 1 N/2 - 1 N/21. 所以要从 N / 2 − 1 N/2 -1 N/21 ~ 1.

删除堆中一个节点

删除节点需要把要删除的节点与最后一个元素交换, 然后递归的调用维持堆的性质.

例如如下例子, 用完全二叉树过程进行解释.
删除节点过程

灰色节点: 已删除节点
绿色节点: 进行逻辑计算节点
箭头: 表示数据交换

堆排序代码

public static void heapSort(int[] data) {
    for (int i = data.length / 2 - 1; i >= 0; i--) {
        adjust(data, i, data.length);
    }
    for (int j = data.length - 1; j > 0; j--) {
        //交换元素
        int temp = data[0];
        data[0] = data[j];
        data[j] = temp;
        adjust(data, 0, j);
    }
}
public static void adjust(int[] data, int i, int length) {
    int temp = data[i];
    // 调整完成之后,也会调整它的子节点
    // i * 2 + 1 是左节点.
    for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
        // 判断指针是否向右节点移动,关系着下一次迭代
        if (k + 1 < length && data[k] < data[k + 1]) {
            k = k + 1;
        }
        if (data[k] > temp) {
            data[i] = data[k];
            i = k;
        } else {
            break;
        }
    }
    data[i] = temp;
}

优先队列

优先队列是堆排序的一个应用. 优先队列的删除和增加一个节点都在 O ( l o g 2 N ) O(log_2N) O(log2N), 具体操作的时间主要是花费在查找删除的元素和维护堆的性质.

优先队列一般操作都是从数组开头删除元素(取)元素, 所以查询元素时间为 O ( 1 ) O(1) O(1),但是需要维护堆的性质, 维护堆的性质需要花费 O ( l o g 2 N ) O(log_2N) O(log2N).

各位观众, 该文章已经临近尾声, 弱弱的求赞.

总结

  1. 最小堆和最大堆的核心概念
  2. 堆排序
  3. 优先队列的核心概念
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值