二叉堆

\(\Huge 堆\)

堆是一种能够维护 \(N\) 个元素的大小关系的树形数据结构。

堆主要分为二叉堆、左偏树、配对堆、斐波那契堆、二项堆等,其中,斐波那契堆以超高的常数复杂度及其巨复杂的实现方式,在此不讲解

下面讲如何实现堆(以大根堆为例):

二叉堆

二叉堆是实现最简单的堆。

1.二叉堆的定义

二叉堆本质上是一棵完全二叉树。

2.二叉堆的实现

如下是一颗完全二叉树:

![](file:///C:/Users/HW/Pictures/R-C.gif?msec=1721108804003)

可以发现,每个左子节点编号都是其父节点的 \(2\) 倍,每个右子节点编号都是其父节点的 \(2\)\(+1\)

因此,我们可以用一个数组存储这个完全二叉树,并利用上述特征取出每个父节点与子节点的数值。

为了防止越界的情况,还要用一个数来保存编号最大的节点的编号。

int heap[N],n;

我们不妨保证根节点永远是二叉堆内部最大的数:

  • 当插入新节点时更新上面的节点。

    • 观察新节点的父节点(编号 \(\lfloor x\div2\rfloor\)

    • 若父节点小于子节点,则交换父节点与子节点的值:

      void Up(int p){
          while(p>1){
              if(heap[p]>heap[p/2]){
                  swap(heap[p],heap[p/2]);
                  p/=2;
              }
              else break;
          }
      }
      void Insert(int x){
          heap[++n]=x;
          Up(n);
      }
      
  • 当排出根节点时更新下面的节点。

    • 观察左子节点与右子节点,取其较大者,与父节点比较,若父节点较小,则交换两数(左右子节点取较大值与父节点交换后必为三者中最大值)。

    • 继续下调:

      void Down(int p){
          int s=p*2;
          while(s<=n){
              if(s<n&&heap[s]<heap[s+1])s++;
              if(heap[s]>heap[p]){
                  swap(heap[s],heap[p]);
                  p=s;
                  s*=2;
              }
              else break;
          }
      }
      void Pop(){
          heap[1]=heap[n--];
          Down(1);
      }
      

接下来就可以放心输出堆顶了。

int GetTop(){
    return heap[1];
}

当然,万能的 \(STL\) 也为我们提供了一个等价于堆的优先队列:

priority_queue<int,vector<int>,less<int> >q;//大根堆
priority_queue<int,vector<int>,greater<int> >q;//小根堆

但是相较于手写堆来说,优先队列缺少了随机删除操作,即删除任意一个任意编号的数:

注意:此时可能既需要下调,也可能上调,都需要执行一次:

void Remove(int k){
    heap[k]=heap[n--];
    Up(k);Down(k);
}

3.二叉堆的应用

例题一、堆

题意:给定一个数列,初始为空,请支持下面三种操作:

  1. 给定一个整数 \(x\) ,请将 \(x\) 加入到数列中。
  2. 输出数列中最小的数。
  3. 删除数列中最小的数(如果有多个数最小,只删除 \(1\) 个)。

此题是堆的板子,直接模拟。

#include<bits/stdc++.h>
using namespace std;
int heap[1000010],n,m;
void Up(int p){
    while(p>1){
        if(heap[p]>heap[p/2]){
            swap(heap[p],heap[p/2]);
            p/=2;
        }
        else break;
    }
}
int GetTop(){
    return heap[1];
}
void Insert(int x){
    heap[++n]=x;
    Up(n);
}
void Down(int p){
    int s=p*2;
    while(s<=n){
        if(s<n&&heap[s]<heap[s+1])s++;
        if(heap[s]>heap[p]){
            swap(heap[s],heap[p]);
            p=s;
            s*=2;
        }
        else break;
    }
}
void Pop(){
    heap[1]=heap[n--];
    Down(1);
}
int main(){
    scanf("%d",&m);
    while(m--){
        int op,x;
        scanf("%d",&op);
        if(op==1){
            scanf("%d",&x);
            Insert(x);
        }
        if(op==2) printf("%d\n",GetTop());
        if(op==3) Pop();
    }
    return 0;
}

例题二、合并果子

题意:给定 \(N\) 个数,不断取出两个数求和再放入并累加到答案中,输出最小的答案。

分析:将 \(N\) 个数放入小根堆,不断取出前两小的数,求和并累加至答案中且将和再次插入小根堆,直至堆内元素只剩一个:

#include<bits/stdc++.h>
using namespace std;
int heap[1000010],n,m,num,ans;
void Up(int p){
    while(p>1){
        if(heap[p]<heap[p/2]){
            swap(heap[p],heap[p/2]);
            p/=2;
        }
        else break;
    }
}
int GetTop(){
    return heap[1];
}
void Insert(int x){
    heap[++n]=x;
    num++;//记录堆中元素数 
    Up(n);
}
void Down(int p){
    int s=p*2;
    while(s<=n){
        if(s<n&&heap[s]>heap[s+1])s++;
        if(heap[s]<heap[p]){
            swap(heap[s],heap[p]);
            p=s;
            s*=2;
        }
        else break;
    }
}
void Pop(){
    heap[1]=heap[n--];
    num--;//记录堆中元素数
    Down(1);
}
int main(){
    scanf("%d",&m);
    while(m--){
        int x;
        scanf("%d",&x);
        Insert(x);
    }
    while(num>1){
        int x1=GetTop();
        Pop();
        int x2=GetTop();
        Pop();
        ans+=x1;
        ans+=x2;
        Insert(x1+x2);
    }
    printf("%d\n",ans);
    return 0;
}

posted on 2024-07-17 01:05  zengziquan  阅读(2)  评论(0编辑  收藏  举报

导航