数据结构之 —— 堆

一、定义

堆是一种特殊的树结构,堆是一个完全二叉树,并且堆中的每个节点必须大于等于(或小于等于)其子树中的每个节点的值。对于大于等于其左右节点的堆称为“大顶堆”;小于等于其左右节点的堆称为“小顶堆”。

二、存储方式

因为堆是一个完全二叉树的结构,所以可以直接使用数组来进行保存数据,从数组的下标1开始存储数据节点,左子树存储的位置就是2*i的位置,右子树的位置就是2*i + 1的位置进行存储。

三、堆提供的操作

向堆中添加一个元素:插入数据后,为了保持堆的特性,需要对现有的结构进行调整,这个过程就称为堆化。堆化的过程又分为两种,一种是从上往下,一种是从下往上;堆化过程非常简单,就是顺着节点所在的路径,向下或者向上移动数据。

public void insert(int data) {
    if (count >= n) {
        return;
    }
    count++;
    a[count] = data;
    int i = count;
    // 自下往上进行堆化
    while (i/2 > 0 && a[i] > a[i/2]) {
        // 数据进行交换
        swap(a, i, i/2);
        i = i/2;
    }
}

删除堆顶元素:在堆中,堆顶元素就是整个堆中最大值(最小值),删除元素可以将堆中的最后一个元素与堆顶元素进行互换,然后在自上向下进行堆化,保证堆的两个特性;堆化的时间复杂度,也就是树的高度,所以时间复杂度为O(logn)。

public void removeMax() {
    if (count == 0) {
        return ;
    }
    a[1] = a[count];
    --count;
    heapify(a, count, 1);
}

private void heapify(int[] a, int count, int i) {
    while (true) {
        int maxPos = i;
        if (i*2 < n && a[i] < a[i*2]) {
            maxPos = i*2;
        }
        if (i*2 + 1 < n && a[maxPos] < a[i*2 + 1]) {
            maxPos = i*2 + 1;
        }
        if (maxPos == i) {
            break;
        }
        // 数据交换
        swap(a, maxPos, i);
        i = maxPos;
    }
}

基于堆进行排序:使用堆排序的时间复杂度为O(nlogn),并且堆排序为原地排序,堆排序的过程大致可以分为两步,分为建堆和排序。

  • 建堆:就是使用原本的数组进行构建一个符合堆的要求的数据,并且这个过程不借助新的数组;建堆的时间复杂度为O(n)
private void buildHeap(int[] a, int n) {
    for (int i = n/2; i>=1; i--) {
        heapify(a, n, i);
    }
}

private void heapify(int[] a, int count, int i) {
    while (true) {
        int maxPos = i;
        if (i*2 < n && a[i] < a[i*2]) {
            maxPos = i*2;
        }
        if (i*2 + 1 < n && a[maxPos] < a[i*2 + 1]) {
            maxPos = i*2 + 1;
        }
        if (maxPos == i) {
            break;
        }
        swap(a, maxPos, i);
        i = maxPos;
    }
}
  • 排序:建堆完成后,最大的数据已经在堆顶了,这是时候我们将堆顶的元素与最后一个元素进行位置互换,然后将n-1的数据在进行建堆操作,重复上述步骤,直到数据全部排序完成,这就是堆排序的过程。
public void sort(int[] a, int n) {
    buildHeap(a, n);
    int k = n;
    while (k > 1) {
        swap(a, 1, k);
        --k;
        heapify(a, k, 1);
    }
}

四、堆的应用场景

  1. 实现优先级队列:普通的队列是满足先进先出的特性,但是优先级队列是优先级高的先出队列,一个堆就可以看做是一个优先级队列,往优先级队列中插入一个元素,就相当于往堆中插入一个元素;而从队列中取出优先级高的队列,就相当于从堆顶删除一个元素
  2. 利用堆求解Top K:我们可以维护一个K大小的小顶堆,然后顺序遍历数组,与堆顶进行比较,当大于堆顶数据时,就替换堆顶元素,然后重新堆化,这样重复步骤,遍历数据的时间复杂度为O(n),一次堆化需要的时间复杂度为O(logK),总的时间复杂度为O(nlogK)
  3. 用堆求中位数:这时候我们需要手动维护两个堆,一个大顶堆,一个小顶堆,如果一个新加入的元素小于大堆顶,就将其插入到大堆顶中,否则就插入到小堆顶中,当两个堆中的数据不相等时,我们就需要从一个堆中的数据插入到另一个堆中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值