【C/C++】大小顶堆(插入和删除)

只是一个鶸用来记录学习内容的文章罢了,如果能多少帮到你那真是太好了,发现错误欢迎指正。

堆总是满足一下特性:
1.堆中某个结点的值总是不大于或不小于其父结点的值。
2.堆总是一棵完全二叉树。
一般用数组描述堆结构并用下标对其进行操作。

完全二叉树的数组形式就是将节点按层依次放入数组中。
比如下图中的树
在这里插入图片描述
用数组描述就是这样的
在这里插入图片描述
由此不难发现:
若已知父节点下标x,则左孩子下标为x* 2+1,右孩子下标为x* 2+2。
若已知孩子下标为y,则其父节点下标为(y-1)/2。因为下标都是int类型所以右孩子按照此法算出来跟左孩子一样也是对的。

大顶堆就是父节点都大于孩子节点的堆,小顶堆就是父节点都小于孩子节点的堆。

插入元素

插入元素的过程中需要保证每次插入元素之后堆的结构都不被破坏。

方法是先将待插入元素添加至数组末尾(用的是STL的双端数组deque),然后从堆最末尾也就是完全二叉树的叶子节点向上探测看是否出现不满足堆特性的情况。

先初始化current和parent作为当前结点和当前结点的父节点的下标,比较父节点和待插入元素,若不符合堆特性(详情见注释)则用父节点的值覆盖当前节点,然后继续重复此动作直到找到待插入位置或当前结点没有父节点结束循环。这样父子节点就根据大小关系排好了。

void heap_insert(deque<int>* heap,int insert_number)
{
    heap->push_back(insert_number);//先将元素插在堆末尾
    int current = heap->size()-1,parent;
    while(current > 0)
    {//从下往上检验是否满足堆的特性
        parent = (current-1)/2;
        //以大顶堆为例,寻找新元素真正的插入位置时需要一直向上探测,比较待插入元素和当前节点的父节点大小如何
        //如果待插入元素比当前结点的父节点还要大则父节点覆盖当前结点,然后当前结点指针继续向上,循环步骤
        if(insert_number > heap->at(parent))
            heap->at(current) = heap->at(parent);//当前结点的父节点覆盖当前结点
        //如果待插入元素不比当前结点的父节点大,则当前结点即为待插入的位置,跳出循环
        else
            break;
        current = parent;//当前结点指针变为父节点,准备继续向上探测
    }
    //跳出循环则意味着找到了待插入位置,直接将当前结点处的值修改为待插入元素即可
    heap->at(current) = insert_number;
}

删除元素(堆顶)

删除堆顶元素需要首先记录堆顶元素,之后将堆末尾最后一个元素覆盖为堆顶元素,然后从堆顶开始向下探测,若不符合堆特性(详情见注释)则交换父节点和子节点然后继续重复此动作,由此来保证删除元素后堆的结构保持不变,最后返回之前记录的堆顶元素。

int heap_pop(deque<int>* heap)
{
    int ret = heap->at(0);//记录堆顶元素
    //最后一个元素覆盖第一个元素
    heap->at(0) = heap->at(heap->size()-1);
    heap->pop_back();
    
    int current = 0,left_child,right_child;
    bool IsLeft;
    while(1)
    {
        IsLeft = true;//左孩子大于右孩子么?
        left_child = current*2+1;
        right_child = current*2+2;
        if(left_child >= (int)heap->size()-1)
            break;//越界跳出循环
        if(heap->at(left_child) < heap->at(right_child))
            IsLeft = false;//右孩子大
        if(IsLeft && heap->at(current) < heap->at(left_child))
        {//左孩子大且父节点小于左孩子,交换父节点和左孩子
            swap(heap,current,left_child);
            current = left_child;//更新当前结点
        }
        else if(!IsLeft && heap->at(current) < heap->at(right_child))
        {//右孩子大且父节点小于右孩子,交换父节点和右孩子
            swap(heap,current,right_child);
            current = right_child;//更新当前结点
        }
        //因为每个节点都满足堆特性
        //所以如果有一处的父节点大于所有的孩子则往下的所有的子节点都小于这个父节点因此可以不用往下看直接跳出循环
        else
            break;
    }

    return ret;
}

贴上所有测试代码

#include <bits/stdc++.h>
using namespace std;

void swap(deque<int>* heap,int a,int b)
{
    int temp;
    temp = heap->at(a);
    heap->at(a) = heap->at(b);
    heap->at(b) = temp;
}

void heap_insert(deque<int>* heap,int insert_number)
{
    heap->push_back(insert_number);//先将元素插在堆末尾
    int current = heap->size()-1,parent;
    while(current > 0)
    {//从下往上检验是否满足堆的特性
        parent = (current-1)/2;
        //以大顶堆为例,寻找新元素真正的插入位置时需要一直向上探测,比较待插入元素和当前节点的父节点大小如何
        //如果待插入元素比当前结点的父节点还要大则父节点覆盖当前结点,然后当前结点指针继续向上,循环步骤
        if(insert_number > heap->at(parent))
            heap->at(current) = heap->at(parent);//当前结点的父节点覆盖当前结点
        //如果待插入元素不比当前结点的父节点大,则当前结点即为待插入的位置
        else
            break;
        current = parent;//当前结点指针变为父节点,准备继续向上探测
    }
    //跳出循环则意味着找到了待插入位置,直接将当前结点处的值修改为待插入元素即可
    heap->at(current) = insert_number;
}

int heap_pop(deque<int>* heap)
{
    int ret = heap->at(0);//记录堆顶元素
    //最后一个元素覆盖第一个元素
    heap->at(0) = heap->at(heap->size()-1);
    heap->pop_back();
    
    int current = 0,left_child,right_child;
    bool IsLeft;
    while(1)
    {
        IsLeft = true;//左孩子大于右孩子么?
        left_child = current*2+1;
        right_child = current*2+2;
        if(left_child >= (int)heap->size()-1)
            break;//越界跳出循环
        if(heap->at(left_child) < heap->at(right_child))
            IsLeft = false;//右孩子大
        if(IsLeft && heap->at(current) < heap->at(left_child))
        {//左孩子大且父节点小于左孩子,交换父节点和左孩子
            swap(heap,current,left_child);
            current = left_child;//更新当前结点
        }
        else if(!IsLeft && heap->at(current) < heap->at(right_child))
        {//右孩子大且父节点小于右孩子,交换父节点和右孩子
            swap(heap,current,right_child);
            current = right_child;//更新当前结点
        }
        //因为每个节点都满足堆特性
        //所以如果有一处的父节点大于所有的孩子则往下的所有的子节点都小于这个父节点因此可以不用往下看直接跳出循环
        else
            break;
    }

    return ret;
}

int peek(deque<int> heap)
{
    if(heap.size() == 0)
    {
        cout<<"空堆"<<endl;
        return -1;
    }
    return heap[0];
}

void show_heap(deque<int> heap)
{
    cout<<"堆内部大致情况如下\n";
    int line_sum = 1,x = 1,line = 1;
    for(int i = 1;i<(int)heap.size();i=i*2+1)
        line_sum++;
    for(int i = 0;i < line_sum;i++)
        cout<<"  ";
    for(int i = 0;i < (int)heap.size();i++)
    {
        cout<<heap[i]<<" ";
        if(i+1 == x)
        {
            cout<<endl;
            for(int j = 0;j < line_sum-line;j++) cout<<"  ";
            x = x*2+1;
        }
        line++;
    }
    cout<<endl;
}

int main()
{
    deque<int> heap;

    printf("请输入待放入堆中的元素,以-1结尾\n");
    int in;
    while(1)
    {
        cin>>in;
        if(in == -1) break;
        heap_insert(&heap,in);
    }
    show_heap(heap);
    while(1)
    {
        if(heap.size() == 0) break;
        cout<<"  "<<heap_pop(&heap)<<" "<<endl;
        show_heap(heap);
    }
    return 0;
}

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值