二叉堆

二叉堆这种数据结构对于优先队列的实现是如此的普遍。

二叉堆有两个性质:结构性和堆序性

二叉堆的定义:

二叉堆是一棵完全二叉树。底层上的元素看做从左到右填入。二叉堆简称


因为完全二叉树很有规律,所以它可以用一个数组来表示而不需要指针。对于数组上任一位置i的元素,其左儿子位置为2i,右儿子位置(2i+1)。则它的父亲在位置[ i / 2]上。



所以堆这种数据结构的声明,由一个数组(关键字不管什么类型),一个代表最大值的整数以及当前堆的大小组成。

struct Heap
{
    int capacity;
    int size;
    int emt[maxn];  //数组 
};


堆序性:

在一个堆中,对于每一个节点X,X的父亲中的关键字小于(或等于)X中的关键字,根节点除外(它没有父亲)。[也可以大于(或等于)看你怎么建堆了]

按照此性质,所以一个堆的任意子树也应该是一个堆,任意节点小于(或等于)它的所以后裔。

最小元总可以在根节点找到。


堆的初始化:

void init(Heap *H)
{
    H->capacity = maxn; //maxn是数组的边界
    H->size = 0;
    H->emt[0] = MIN; //标记 MIN = -INF
}

堆的基本操作:

Insert()插入

为将一个元素X插入到堆中,我们在下一个空闲位置创建一个空穴(否则该堆就不是完全树了)。如果X放在该空穴中,而不破坏堆序性,那么插入完成。否则,我们把空穴的父亲节点元素移入到该空穴,这样,空穴就朝根的方向上行了一步。

继续改过程直至X能放入到空穴为止。

这种策略叫做 上滤

insert()插入的时间复杂度是O(log N)(最坏情况)

void insert(int x, Heap *H) //插入 
{
    int i;
    if (isFull(H))
    {
        cout<<"Qriority queue is full."<<endl;
        return ;
    }
    H->size++;
    for (i = H->size; H->emt[i/2] > x; i/= 2)
    {
        H->emt[i] = H->emt[i/2]; //空穴上滤 
    }
    H->emt[i] = x; 
}

MIN标记:

如果要插入的元素是新的最小值,那么它将一直被推向顶端。这一时刻,i 等于1,我们就需要令程序跳出循环。我们当然用可以明确的条件判断来做到。但是,我们采用的是把一个很小的值放在emt[0] 的位置以使循环终止,这个值必须保证小于堆中的任何值。这个值称之为 标记(sentinel)。这样避免了每个循环都要执行的条件判断,从而节省了时间。

DeteleMin()删除最小元

找到最小元是容易的,困难的是删除它。

当删除一个最小元时,在根节点处产生了一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。我们讲空穴的两个儿子中的较小者移入空穴,这样,就把空穴向下推了一层。重复该步骤,直到X可以被放入到空穴中。

因此,我们的做法是把X置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。

这种策略叫做 下滤

最坏情况,根处的元素下滤到堆的底层。时间复杂度为O(log N).

void deleteMin(Heap *H) //删除最小元素 
{
    if (isEmpty(H))
    {
        cout<<"Qriority queue is empty."<<endl;
        return ;
    }
    int lastemt, child, minemt, i;
    minemt = H->emt[1];
    lastemt = H->emt[H->size--];
    for (i = 1; i*2 <= H->size; i=child)
    {
        child = i * 2;
        if (H->emt[child+1] < H->emt[child])//选儿子结点中较小者 
        {
            child++;
        }
        if (H->emt[child] < lastemt)
        {
            H->emt[i] = H->emt[child];  //空穴下滤 
        }
        else break; //这句话不能省(当lastemt < H->emt[child]的时候,马上break;紧接着lastemt移到上一次child的位置) 
    }
    H->emt[i] = lastemt;
    cout<<"Current min key "<<minemt<<" has been deleted successful."<<endl;
    return ;
}
buildHeap()构建堆

建堆最简单的方法,就是把N个关键字作为输入并把他们插入到空堆中。

使用N个相继的insert()插入操作来完成。

由于每个insert()将花费O(1)平均时间以及O(log N)的最坏情形时间,因此算法的总的运行时间是O(N)平均时间。

void buildHeap(Heap *H, int key[])//建堆 
{
    for (int i = 0; i < N; i++)
    {
        insert(key[i],H);
    }
}

其他堆操作:

虽然求最小值操作可以在常数时间完成,但是,按照最小元设计的堆(也称作最小值堆)在求最大元时却无任何帮助。

我们只知道该最大元素在树叶上,但是半数的元素都位于树叶上。因此该信息是没什么用的。


方法:(下述三种操作对数最坏情形时间运行)

DecreaseKey(降低关键字的值)

通过降低位置P关键字的值,由于这可能破坏堆序性,因此会通过上滤对堆进行调整。

该操作对于系统管理员是有用的,可以使他们的程序以最高的优先级来运行。

IncreaseKey(增加关键字的值)

通过增加位置P关键字的值,下滤来完成。

许多调度程序自动降低正过多消耗CPU时间的进程的优先级。

Delete(删除)

此操作删除堆中位置P的节点

通过先执行DecreaseKey操作,然后再执行DeleteMin操作来完成。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值