【C++】优先队列详细讲解(原理+STL库调用)

一、堆以及建堆函数

优先队列的核心思想之一就是堆排。但是注意!堆可不是堆栈的堆
【定义】堆,其实是用vector组成的完全树(因为完全树再层序遍历的时候就是用向量也可以完美表现)。其思想就是先构建出堆,然后会出现一个顶点一定为(最大或最小)的偏序。然后每次把顶点拿走之后再下滤即可。
【步骤】首先根据性质定义好构造建堆下数据结构中优先队列的性质,里面是完全二叉树的性质)

下滤函数–主要使用递归(用于下文的建堆和删顶)

因为主要用递归,所以要记住其需要的三个参数:加&的vector,这个里面放着建堆用的数据。i表示父节点,也是要将左右两个子树合并起来的关键。最后一个i合并建堆就结束了。heapsize是为了确保过程中,不要过了vector的界(其实这个size一直就是vector的size)。
1、先根据性质找到左右子节点的位置:由于父节点为 i i i
左子节点为: 2 i + 1 2i+1 2i+1右子节点: 2 i + 2 2i+2 2i+2
2、父节点将与左右子节点大于它并且更大的那个进行互换
(注意此处有两个条件,不仅要大于父节点,他俩还只能取一次,此时技巧来了!)
2.2定义一个large = i i i,然后large会分别与左右节点比较,并只要大于它,large就等于他。这样两个if过后,它就是最大的那个了。
2.3上一小步中定义两个if,一个条件是左右别超界爆炸,另一条件判断左右在vector中是否大于父节点,如都不大直接别判断了。
2.4 此时如果large不是i,即存在有比他大的子节点了,就直接swap一下,然后接着进行递归。即下一个继续传入a,但是父节点就是large了,其中size一直不变
【ps】难道large为i就不递归了吗,还真就是这样,不递归了!
记忆代码:

  void low_update(vector<int>& nums, int i,int heap_size) {
        int left = 2*i+1;int right = 2*i+2;int large  = i;
        if(left < heap_size && nums[left]>nums[large]) large = left;//由此看出是升序
        if(right < heap_size && nums[right]>nums[large]) large = right;
        if(large!=i)
        {
            swap(nums[i],nums[large]);
            low_update(nums, large, heap_size);
        }
    }

建堆函数

【思想】根据floyd算法,自下而上的下滤,从下面两两合并最终合成一个完整树。
其实就是从下向上的拿vector倒序建,建完整体没顺序,但是只要符合操作上下滤,顶点就一直是极值。就一个for循环,从后往前传一半的i就行了!
因此for(int i = n/2;i>=0;i–)!!!其他没孩子无所谓了。

void build_heap(vector<int>& nums,int n) {
        for(int i = n/2;i>=0;i--)
        {
            low_update(nums,i,n);
        }
    }

三、删顶(取一次删一次,取几次删剩下的就是要求的了)
注意是涉及到上滤的
1、先建堆–此后vector可就是堆的形状了!
2.1、pop出去【其实仔细想想不知道如何pop呀】但是联系下一步有个妙招
2.2、头跟尾巴交换位置,再下滤
3、将两个第二步融合一下,就是将头和尾巴交换位置,然后size-1之后,放入下滤就ok了!
最后,返回头,也就是nums[0]

二、调用在stl库里面的优先队列priority_queue

首先,需要引入库#include <queue>。下面将分别根据建堆所需的数据类型情况进行分类描述:
首先,大根堆就是每次取最大值,小根堆就是每次取最小值。但是,对于最大和最小堆栈在定义排序顺序时,是与sort中的逻辑正好相反的。(如在下面情况1中,greater<int>对应的是最小堆,而在sort中的greater<int>()对应的是从大到小排!原因可见情况3讲解)

建堆

1、单个数的情况

//小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//大顶堆
priority_queue <int,vector<int>,less<int> >q;
//默认大顶堆
priority_queue<int> a;

里面可以是正常int,但更多的是pair,会对前面的进行排序,取后面的就可以!

2、两个数pair的情况

pair的比较,先比较第一个元素,第一个相等比较第二个

//小顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> q;
//大顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>, less<>> q;
//默认大顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>> q;

当pair想比较第二个的时候,可以选择前后切换即可。

3、自定义类型的情况

若要使相对树的节点等不是Int类型的使用优先队列,则可以通过重定义操作符"<",以链表的节点为例,可以这样声明优先队列。

 struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }//注意这个是最小堆,最小堆是>;最大堆是<,两个堆都需要重定义<
    };
    priority_queue <Status> q//这样就能直接声明定义了

由于堆原理中,默认是下面的左右子节点大于上面的父节点,便让父节点跟最大的那个换一下,而此处修改便是找最大的那个条件,即该结构中源代码是<,而编写的新仿函数或者重载<,使其return 大于的才对,就是将<变成了>。因此就会从默认的由小到大排转成了由大到小排

4、仅仅想重构“比较规则”的情况

可以使用重构()函数的方法(其逻辑比较顺序与上面的重构<函数一模一样):

    struct cmp1
        {
            bool operator()(char &a,char &b) { return  a < b; }//这是大根堆
            //注意这里定义的逻辑跟sort定义是反着的
    	};
    struct cmp2
    {
    	bool operator()(pair<double,vector<int>>&p1,pair<double,vector<int>>&p2)
		{
			return p1.first > p2.first;//小顶堆是大于号
		}
    };
    priority_queue<char,vector<char>,cmp1> a;//例如a中有'a'和'b',此时top出来的是b
    priority_queue<pair<double,vector<int>>,vector<pair<double,vector<int>>>,cmp2> b;

5、想通过sort那样使用匿名函数快速定义排序顺序

在 priority_queue 的构造函数中,可以传入一个比较函数作为参数,用于指定元素的优先级比较方式。
因此我们可以先声明一个匿名函数,随后使用decltype函数来使该函数变为一个type从而使用。

auto cmp = [&](const int& a, const int &b) {
    return cnt[a] < cnt[b];//此处cnt可由上文完成定义(最大堆--跟sort正好相反)
};
priority_queue<int, vector<int>, decltype(cmp)>pq{cmp};

堆操作

以上面堆栈a为例:
加入堆节点:a.push('a');
删除堆顶节点:a.pop();
取出堆顶节点:a.top();

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值