特殊的完全二叉树-堆(神奇的队列优化)

满二叉树:高度为h,结点数为2*h-1。
完全二叉树:高度为h,1~h-1层结点数满,第h层从右到左缺若干结点。若有N个结点,高度为log2N

最小堆:在完全二叉树的基础上,所有的父结点都比子结点小。
最大堆:在完全二叉树的基础上,所有的父结点都比子结点大。

问题引入:14个数:99 5 36 7 22 17 46 12 2 19 25 28 1 92,若删除其中最小的元素,并新增k,重复14次
普通的数组遍历:O(n*n)

引入最小堆
将14个树构造最小堆,用h数组存储,h[1]就是最小的。
将堆顶删除,把k放入堆顶,向下调整,重新构造出最小堆。O(logN)

向下调整

void swap(int x,int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
    int t,flag=0;/*flag用来标记是否需要继续向下调整*/
    while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
    {
        if(h[i]>h[i*2])/*首先判断它和左儿子的关系,并用t记录值较小的结点编号*/
            t=i*2;
        else
            t=i;
        if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
        {
            if(h[t]>h[i*2+1])
                t=i*2+1;
        }
        if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
        {
            swap(t,i);
            i=t;/*更新i为刚才与它交换的儿子结点的编号*/
        }
        else
            flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
    }
}

新增一个数,插入到末尾,向上调整,使其符合最小堆的特性。

向上调整

void siftup(int i)/*传入一个需要向上调整的结点编号i*/
{
    int flag=0;/*标记是否需要继续向上调整*/
    if(i==1)/*堆顶*/
        return;
    while(i!=1&&flag==0)
    {
        if(h[i]<h[i/2])
            swap(i,i/2);/*交换它和它爸爸的位置*/
        else
            flag=1;
        i=i/2;/*更新编号i为它父结点的编号*/
    }
}

如何建立堆

用数组h存储,把n个数放入完全二叉树中,即放入数组中。
从最后一个节点开始,依次判断以这个结点为根的子树是否满足堆的特性
若所有的子树都满足堆,那么这个完全二叉树就是最小堆或最大堆。
以叶结点为根的子树其实就是叶结点自己,可以不考虑叶结点。O(N)

二叉树的性质:最后一个非叶结点的编号是N/2。
所以可以从第N/2个结点开始。

建堆及其堆排序(建立最小堆)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int h[101];
int n;/*堆的大小*/
void swap(int x,int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
    int t,flag=0;/*flag用来标记是否需要继续向下调整*/
    while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
    {
        if(h[i]>h[i*2])/*首先判断它和左儿子的关系,并用t记录值较小的结点编号*/
            t=i*2;
        else
            t=i;
        if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
        {
            if(h[t]>h[i*2+1])
                t=i*2+1;
        }
        if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
        {
            swap(t,i);
            i=t;/*更新i为刚才与它交换的儿子结点的编号*/
        }
        else
            flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
    }
}
void creat()
{
    int i;
    for(int i=n/2; i>=1; i--) /*从最后一个非叶结点到第1个结点依次进行向上调整*/
        siftdown(i);
}
int deletemax()
{
    int t;
    t=h[1];/*记录堆顶点的值*/
    h[1]=h[n];/*将堆的最后一个点赋值到堆顶*/
    n--;/*堆元素减少1*/
    siftdown(1);/*调整*/
    return t;
}
int main()
{
    int i,num;
    scanf("%d",&num);
    for(int i=1; i<=num; i++)
        scanf("%d",&h[i]);
    n=num;
    creat();/*建堆*/
    for(int i=1; i<=num; i++) /*删除顶部元素,连续删除n次*/
        printf("%d ",deletemax());
    return 0;
}

建堆及其堆排序(建立最大堆)
思想:建立最大堆,最大元素为h[1],从小到大排序,最大的放在后面,将h[1]和h[n]交换。
交换后,h[n]就是数组中最大的元素。
h[1]向下调整保持堆的特性。
最大元素已经归位,n–。
直至堆的大小变成1为止。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int h[101];
int n;/*堆的大小*/
void swap(int x,int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
    int t,flag=0;/*flag用来标记是否需要继续向下调整*/
    while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
    {
        if(h[i]<h[i*2])/*首先判断它和左儿子的关系,并用t记录值较大的结点编号*/
            t=i*2;
        else
            t=i;
        if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
        {
            if(h[t]<h[i*2+1])
                t=i*2+1;
        }
        if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
        {
            swap(t,i);
            i=t;/*更新i为刚才与它交换的儿子结点的编号*/
        }
        else
            flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
    }
}
void creat()
{
    int i;
    for(int i=n/2; i>=1; i--) /*从最后一个非叶结点到第1个结点依次进行向上调整*/
        siftdown(i);
}
void heapsort()
{
    while(n>1)
    {
        swap(1,n);
        n--;
        siftdown(1);
    }
}
int main()
{
    int i,num;
    scanf("%d",&num);
    for(int i=1; i<=num; i++)
        scanf("%d",&h[i]);
    n=num;
    creat();/*建堆*/
    heapsort();
    for(int i=1; i<=num; i++) /*删除顶部元素,连续删除n次*/
        printf("%d ",h[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zaiyang遇见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值