高级数据结构1--堆

本文是高级数据结构系列第1篇。

引入

维护一个数据结构,支持以下操作:
1、插入一个元素;
2、询问所插入元素中的最优值;
3、删除最优的元素。
一般情况下,元素就是一个32位整数,最优元素是最大或最小的整数。

朴素算法:
用类似插入排序的思想,把所有的数插入合适的位置,询问时输出a[1],删除时将所有数前移一位。
不足:时间复杂度O(nq),超时。

更高级的算法:
使用诸如平衡树之类的二叉查找树或01trie树(如果有错,欢迎dalao指正),进行查找、删除。
不足:代码复杂,常数大。

介绍

堆是一棵完全二叉树。
完全二叉树定义如下:
   1、这是一棵二叉树。
   2、除去深度最大的一层,其余几层构成满二叉树。
   3、最后一层的所有节点都连续集中在最左边。
因此,它有如下性质:
   1、按广搜遍历给节点编号后i号节点的父亲编号是i/2,左孩子编号为i*2,右孩子编号为i*2+1。
   2、有n个节点的完全二叉树最大深度为log(n)。
而堆相较于一般完全二叉树有有如下性质:
   每个父节点所存的值比它两个孩子更优。

为方便区分,以下称堆顶元素最小的堆为小根堆,反之为大根堆。

实现

堆可以用一个一维数组模拟,数组的下标表示节点编号,值表示节点所存的数值。其定义一般如下:

struct heap{
    int a[maxn],top;
    //接下来定义各种操作函数
    //为方便说明,设这个堆是小根堆
}

其中top指堆中数字的个数。
接着达成开头所述的要求。
询问插入的元素中最优值就很简单了。

int getbest(){
    return a[1];
}

接着是插入元素。
我们先在数组最后插入这个值,但这有可能打破堆的性质。
为使其重新符合这个性质,需要对其做一个操作:上调。

void up(int cur){
    int i;
    while (cur>1){//确保当前这个节点不是根节点,如果是根便无需上调
        i=cur>>1;//等价于i=k/2
        if (a[i]>a[cur]){//如果当前这个节点的值比父节点的值更小
            swap(a[i],a[cur]);//把当前节点与其父节点对调
            //这样就能保证父节点比其两个孩子的值更小
            cur=i;//原来的父节点变成当前节点
        }
        else return;
    }
}
void insert(int x){
    a[++top]=x;
    up(top);
}

当然,up上调还有更简单的写法:

void up(int cur){
    while (cur>1&&a[cur>>1]>a[cur])
      swap(a[cur>>1],a[cur]),cur>>=1;
}

接下来处理删除操作。
乍眼一看感觉似乎很繁,但是我们可以换一种思路:如果我们让堆的最后一个元素放到堆顶,然后对堆顶元素进行下调,同样可以完成任务。

int Min(int x,int y){
    if (y>top) return x;
    if (a[x]<a[y]) return x;
    return y;
}
void down(){
    int k=1,i;
    while (k<<1<=top){//当k还有孩子,即不是叶节点,才可下调
        i=Min(k<<1,k<<1|1);//k<<1=k*2,k<<1|1=k*2+1
        //i表示k两个孩子中(如果有的话)值最小的孩子编号
        if (a[i]<a[k]){//如果两个孩子中最小的一个比它小
            swap(a[i],a[k]);
            k=i;
        }
        else return;
    }
}
void del(){
    a[1]=a[top];
    a[top--]=0;
    down();
}

Min函数可以用 ? : 简化,在此不再赘述。
至此,三种操作全部完成。

复杂度分析
  • 提取最优元素为O(1)
  • 插入、删除与树最大深度有关,为O(log(n))

因此,总复杂度为O(log(n))。
完整的包:

struct heap{
    int top,f[maxn];
    heap_s (){
        top=0;
        memset(f,0,sizeof(f));
    }
    void up(int x){
        while (x>1&&f[x>>1]>f[x])
          swap(f[x>>1],f[x]),x>>=1;
    }
    int mini(int a,int b){
        return b>top||f[b]>f[a]?a:b;
    }
    void down(){
        int i,k=1;
        while (k<<1<=top){
            i=mini(k<<1,(k<<1)+1);
            if (f[i]<f[k]) swap(f[i],f[k]),k=i;
            else break;
        }
    }
    int getop(){
        return f[1];
    }
    void ins(int x){
        f[++top]=x;
        up(top);
    }
    void del(){
        f[1]=f[top];
        f[top--]=0;
        down();
    }
};

堆排序

一般的思想便是把数组中的元素一个个扔进堆里,再一个个取出。
但有一种简化方式。

for (i=n/2;i;--i)
  down(i);//带有参数的down作用是对编号为i的节点下调。

从最后一个不是叶子的节点下调,这样就能满足堆的性质。

不错的例题

合并果子:直接用小根堆维护即可。
黑匣子:用两个堆分别维护大于等于中位数的数与小于中位数的数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值