定义:【最大树(最小树)】每个节点的值都大于(小于)或等于其子节点(如果有的话)值的
最大树与最小树的例子如下所示,虽然这些树都是二叉树,但最大树不必须是二叉树,最大树或最小树节点的子节点个数可以大于等于2.
               

                                                                                                   1-1最大树 
                            

                                                                                                 1-2 最小树
 定义:【最大堆(最小堆)】最大(最小)的完全二叉树。由于堆是完全二叉树,所以1-1b 不是最大堆,1-2b不是最小堆。注意到堆是完全二叉树(从满二叉树中删除K个元素之后为完全二叉树),拥有n个元素的堆其高度为log2(n+1)(与满二叉树有相同的高度),因此可以在O(height)时间内完成插入和删除操作,则这些操作复杂性为O(log2n)。


最大堆的插入

图1-3a给出了一个具有5个元素的最大堆,由于堆是完全二叉树,当插入一个元素形成6元素时,其结构必如1-3b所示。如果插入的元素值为1,则插入的该元素成为了2的左孩子,相反,若元素的值为5,则该元素不能成为2的左孩子(否则将改变最大树的特性),应把2下移为左孩子,如图1-3c所示,同时还得决定在最大堆中5是否占据2原来的位置,由于父节点上的值为20,大于子节点新插入元素5,因此可以在原2的位置插入新的元素5。假设要插入的元素值不是5而是21,这时,同1-3c一样,把2下移为左孩子,由于21大于根节点20,因此不能在2的位置放入新元素21,因此这时需要再次下移根节点20元素,将其移到其右孩子的位置(2的位置),再将新元素21插入根节点如图1-3d所示。

                                                                                   图1-3 最大堆的插入 
由此可见,插入策略从叶到根只有单一路径,每一层的工作需要耗时Θ(1),因此实现此策略的时间复杂度O(height)=O(log2n),n为节点个数。

最大堆的删除

从最大堆中删除一个元素时,该元素从根部移出,例如从1-3d的最大堆中进行删除操作即是移去元素21,因此最大堆只剩下5个元素。此时1-3d的二叉树需要重新构造,以便仍然为完全二叉树。为此可以移动6中的元素,即2。这样就得到了正确的结构如图1-4a中,但此时根节点为空且元素2不在堆中,如果2直接插入根节点,得到的二叉树不是最大树,根节点的元素应该为大于左右子节点值得元素,这个元素值应为20,把它移到根节点,3的位置空了,2可以插入,最后形成的最大堆如1-3a所示。

现在假设要删除20,在删除之后,堆的二叉树结构如图1-4b所示,为得到这个结构,10从位置5移出,如果10放在根节点,结果并不是最大堆。把根节点的两个孩子(15和2)中较大一个移到根节点。假设将10插入2的位置,结果仍然不是最大堆,因此将14上移,10插入4位置,最后结果如1-4c所示。

                                                                      1-4 最大堆的删除

删除策略从堆的根到叶节点的单一路径,每一层的工作需要耗时Θ(1),因此实现此策略的时间复杂度O(height)=O(log2n),n为节点个数,与插入相同。

最大堆的初始化

假设开始数组a中有n个元素,另有n=10,a[1:10]中元素的关键值为[20,12,35,15,10,80,30,17,2,1],这个数组可以用来表示图1-5a的完全二叉树,这棵二叉树不是最大树。为了将其转化成最大堆,从第一具有孩子的节点开始(即节点10),这个元素在数组中的位置为i=[n/2],如果以这个元素为根的子树已经是最大堆,则此时不需要调整,否则必须调整子树使其成为最大堆。随后继续检查以i-1,i-2等节点为根的子树,直到检查到整个二叉树的根节点(其位置为1)。

过程如下,最初i=5,由于10大于其子节点的元素值1,所以位置i=5为根的子树已经是最大堆。

                 检查i=4的子树,由于15<17,因此不是最大堆,将15与17进行交换得树如图1-5b;

                 检查i=3的子树,由于35<80,因此不是最大堆,将35与80进行交换;

                 检查i=2的子树,因为12<17,17成为重构子树的根,下一步将12与位置4的两个孩子中的较大者进行比较,由于12<15,15被移到4位置,12移到15的位置。形成二叉树如图1-5c;

                 检查i=1子树,这时以位置2或者位置3为根的子树已是最大堆了,然而,20<max(17,80),80移入根,位置3空出,由于20<max(35,30),较大者35移入作为子树根,20代替35的位置。如图1-5d。

                    
                                                                         图1-5最大堆的初始化

  代码实现

以下程序给出了最大堆的类定义。n 是私有成员,代表目前堆中元素的个数; MaxSize是堆的最大容量;heap为存贮堆元素的数组,省缺堆的大小为1 0个元素 

template<class T>
class MaxHeap {
public:
    MaxHeap(int MaxHeapSize = 10);
    ~MaxHeap() {delete [] heap;}
    int Size() const {return CurrentSize;}
    T Max() {if (CurrentSize == 0) throw OutOfBounds();
    return heap[1];}
    MaxHeap<T>& Insert(const T& x);
    MaxHeap<T>& DeleteMax(T& x);
    void Initialize(T a[], int size, int ArraySize);
private:
    int CurrentSize, MaxSize;
    T *heap; // 元素数组
} ;
template<class T>
MaxHeap<T>::MaxHeap(int MaxHeapSize){
    // 构造函数
    MaxSize = MaxHeapSize;
    heap = new T[MaxSize+1];
    CurrentSize = 0;
}

插入

template<class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x){
    // 把 x 插入到最大堆中
    if (CurrentSize == MaxSize)
    throw NoMem(); // 没有足够空间
    / /为 x寻找应插入位置
    // i 从新的叶节点开始,并沿着树上升
    int i = ++CurrentSize;
    while (i != 1 && x > heap[i/2]) {
        // 不能够把 x 放入 h e a p [ i ]
        heap[i] = heap[i/2]; // 将元素下移
        i /= 2; // 移向父节点
    }
    heap[i] = x;
    return *this;
}

在插入代码中,i 从新创建的叶节点位置CurrentSize开始,对从该位置到根的路径进行遍历。对于每个位置i,都要检查是否到达根( i = 1)或在i 处插入新元素不会改变最大树的性质(x .key≤h e a p [i/ 2 ] . key)。只要这两个条件中有一个满足,就可以在 i 处插入x,否则,将执行while 循环体,把位于i / 2处的元素移到i 处并把i 处元素移到父节点(i / 2)。对于一个具有n 个元 素的最大堆(即CurrentSize = n),while 循环的执行次数为O(height) =O( log2n),且每次执行所需时间为 ( 1 ),因此Insert 的时间复杂性为O( log2n)。

删除

template<class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
{
    // 将最大元素放入 x ,并从堆中删除最大元素
    // 检查堆是否为空
    if (CurrentSize == 0)
    throw OutOfBounds(); // 队列空
    x = heap[1]; // 最大元素
    // 重构堆
    T y = heap[CurrentSize--]; // 最后一个元素
    // 从根开始,为y 寻找合适的位置
    int i = 1, // 堆的当前节点
    ci = 2; // i的孩子
    while (ci <= CurrentSize) {
        // heap[ci] 应是 i的较大的孩子
        if (ci < CurrentSize && heap[ci] < heap[ci+1]) ci++;
        // 能把 y 放入h e a p [ i ]吗?
        if (y >= heap[ci]) break; // 能
        // 不能
        heap[i] = heap[ci]; // 将孩子上移
        i = ci; //下移一层
        ci *= 2;
    }
    heap[i] = y;
    return *this;
}

在DeleteMax操作中,堆的根(即最大元素)heap [ 1 ]被保存到变量x中,堆的最后一个元素heap [ CurrentSize ]被保存到变量y中,堆的大小(CurrentSize)被减1。在while 循环中,开始查找一个合适的位置以便重新将 y插入。从根部开始沿堆向下查找,对于具有 n 个元素的堆,while 循环的执行次数为O ( lg2n),且每次执行所花时间为 (1),因此,DeleteMax 操作总的时间复杂性为O( log2n)。注意到即使堆的元素个数为 0, 代码也能正确执行,在这种情况下不执行While 循环,对堆的位置1进行赋值是多余的。 

初始化

template<class T>
void MaxHeap<T>::Initialize(T a[], int size, int ArraySize){
    // 把最大堆初始化为数组 a .
    delete [] heap;
    heap = a;
    CurrentSize = size;
    MaxSize = ArraySize;
    // 产生一个最大堆
    for (int i = CurrentSize/2; i >= 1; i--) {
    T y = heap[i]; // 子树的根
    // 寻找放置 y的位置
    int c = 2*i; // c的父节点是y的目标位置
    while (c <= CurrentSize) {
        // heap[c] 应是较大的同胞节点
        if (c < CurrentSize &&
        heap[c] < heap[c+1]) c++;
        // 能把 y 放入h e a p [ c / 2 ]吗?
        if (y >= heap[c]) break; // 能
        // 不能
        heap[c/2] = heap[c]; // 将孩子上移
        c *= 2; // 下移一层
    }
    heap[c/2] = y;
    }
}

初始时删除私有成员heap当前所指的数组,并使 heap指向a [ 0 ]。size为数组a中的元素个数,ArrarySize是假设从a [1]算起数组a 中所能容纳的最大元素个数。程序 的最初4行代码重新设置了最大堆的私有成员,使数组a 代替数组heap。在for 循环中,从数组heap(即数组a)的二叉树表示中最后一个具有一个孩子的节点开始进行初始化,直至到达根节点。对于每个位置 i,在while 循环中都保证根节点为i的子树已是最大堆。请注意for循环体与DeleteMax代码的相似性。                                                            

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值