背包问题 -- 动态规划 -- 堆优化

动态规划:https://blog.csdn.net/libosbo/article/details/80038549
背包问题:https://blog.csdn.net/qq_40778406/article/details/80581238
优先队列:https://blog.csdn.net/shuxiao9058/article/details/7434706

堆是一种特殊的完全二叉树【完全二叉树:一棵二叉树除了最右边位置上一个或者几个叶结点缺少外其它是丰满的】
所有父结点都比子结点要小的完全二叉树我们称为最小堆。反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆。
完全二叉树中父亲和儿子之间有着神奇的规律,我们只需用一个一维数组就可以存储完全二叉树。首先将完全二叉树进行从上到下,从左到右编号。我们发现如果完全二叉树的一个父结点编号为k,那么它左儿子的编号就是2k,右儿子的编号就是2k+1。如果已知儿子(左儿子或右儿子)的编号是x,那么它父结点的编号就是x/2。

【堆的创建】
把n个元素建立一个堆,首先我们可以将这n个结点以自顶向下、从左到右的方式从1到n编码,即以层序遍历的顺序编码,这样就可以把这n个结点转换成为一棵完全二叉树。紧接着从最后一个非叶结点(结点编号为n/2)开始到根结点(结点编号为1),逐个扫描所有的结点,根据需要将当前结点向下调整,直到以当前结点为根结点的子树符合堆的特性。

#include <stdio.h>
#define maxn 105
int h[maxn]; //用来存放堆
int n; //堆的大小
//交换堆中的两个元素的值
void swap(int x,int y)
{
    int t=h[x];
    h[x]=h[y];
    h[y]=t;
}
//向下调整成最小堆
void siftdown(int pos)
{
    int t,flag=0; //flag用来标记是否需要继续向下调整
    while(!flag)
    {
        int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
        if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
        if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
        //如果最小的结点不是父结点
        if(t!=pos)
        {
            swap(t,pos);
            pos=t;
        }
        else flag=1;
    }
}

//建堆
void create()
{
    //从最后一个非叶结点到第1个结点依次进行向下调整
    for(int i=n/2;i>=1;i--)
        siftdown(i);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    create();
    for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);
    return 0;
}

【堆的删除】
若最小(大)堆要进行删除最小(大)数并返回最小(大)数的操作,我们只需要删掉堆顶元素即最小(大)数,将最后一个数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆

//向下调整成最小堆
void siftdown(int pos)
{
    int t,flag=0; //flag用来标记是否需要继续向下调整
    while(!flag)
    {
        int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
        if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
        if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
        //如果最小的结点不是父结点
        if(t!=pos)
        {
            swap(t,pos);
            pos=t;
        }
        else flag=1;
    }
}
//返回并删除最大的元素
int deletemax()
{
    int t=h[1]; 
    h[1]=h[n]; //将堆的最后一个点赋值到堆顶
    n--; //堆的元素减少1
    siftdown(1); //向下调整
    return t; //返回最大值
}

【堆的插入】
若最小(大)堆只需进行插入一个新的数,我们只需要把新的数放在末尾,通过与父亲的比较向上调整即可恢复最小(大)堆。

//向上调整成最小堆
void siftup(int pos) 
{
    int flag=0; //用来标记是否需要继续向上调整
    if(pos==1) return; //如果是堆顶,就不需要调整了    
    //不在堆顶 并且 当前结点pos的值比父结点小的时候继续向上调整 
    while(pos!=1&&!flag)
    {
        //判断是否比父结点的小 
        if(h[pos]<h[pos/2]) swap(pos,pos/2); 
        else flag=1;
        pos=pos/2; //更新编号pos为它父结点的编号 
    }
}

posted @ 2020-10-22 09:56  ginn123  阅读(188)  评论(0)    收藏  举报