c++优先级队列priority_queue使用lambda表达式出错问题

简介: c++优先级队列priority_queue使用lambda表达式出错问题

优先级队列简介


优先级队列priority_queue,可以在队列中自定义数据的优先级, 让优先级高的排在队列前面优先出队。它具有队列的所有特性,包括队列的基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。


优先级队列的内部是大小顶堆实现的,弹出pop()和队首top()都是获得堆首(根结点)的元素。


std::less<T>变成大顶堆(从上层到下层,堆元素是从大到小,同层之间随便)


std::greater<T>变成小顶堆(从上层到下层,堆元素是从小到大,同层之间随便)



基本操作有:


empty( )  //判断一个队列是否为空


pop( )  //删除队顶元素


push( )  //加入一个元素


size( )  //返回优先队列中拥有的元素个数


top( )  //返回优先队列的队顶元素


优先队列的时间复杂度为O(logn),n为队列中元素的个数,其存取都需要时间。


⼆叉堆是⼀种特殊的⼆叉树(完全⼆叉树),因为堆是基于完全二叉树,所以我们不需要用链式结构来表示,我们可以直接用数组存。


假设父节点的下表为parent,从父节点获取子节点:


左节点下标: 2*parent+1


右节点下标: 2*parent+2


假设子节点的下标为son(左右子节点都可以):


父节点下标:(son-1)/2


小顶堆图示:


小顶堆的建立和删除都是 下沉 操作,添加做的是 上浮 操作。



大顶堆图示:



如上图所示,假设父节点的下标 parent=0,则其左子节点下标为Lchildren=2*parent+1。


右子节点下标为Rchildren=2*parent+2,如上示为例:


第 0 个数据的下标:parent = 0


第 1 个数据的下标:Lchildren = 2*parent + 1 = 2*0+1 = 1


第 2 个数据的下标:Rchildren = 2*parent + 2 = 2*0+2 = 2


⼀般的链表⼆叉树,我们操作节点的指针,⽽在数组⾥我们把数组索引作为指针。



问题描述


在c++17下,priority_queue优先级队列使用lambda表达式,可能遇到以下错误提示信息:


error: a lambda expression cannot appear in this context。


测试创建了一个自定义的优先级队列,测试代码如下:


#include <iostream>
#include <queue>
int main() {
  std::cout << "hello test" << std::endl;
  using Ty = std::pair<std::string,int>;
  std::priority_queue<Ty,
        std::vector<Ty>,
        decltype( [](Ty a, Ty b)->bool{
                   return a.second > b.second;
        })> q;
  q.emplace(std::make_pair("yang",3));
  q.emplace(std::make_pair("yong",2));
  q.emplace(std::make_pair("zhen",1));
  std::cout << "q.top()=" <<q.top().first <<std::endl;
  return 0;
 }


这段代码在c++20下面测试是ok的。附测试网站地址:GDB online Debugger | Compiler - Code, Compile, Run, Debug online C, C++



换成c++17下测试:



原因分析


Your code is valid C++20 as written but invalid C++17 or earlyer.

可能你使用了c++20的特性,在c++20之前不支持。


在 C++20 之前闭包类型不是默认可构造的。在 C++20 中没有捕获的闭包类型是默认可构造的。


参考这个回答:

C++: lambda-expression in unevaluated context


c++11 - C++: lambda-expression in unevaluated context - Stack Overflow正在上传…重新上传取消https://stackoverflow.com/questions/52734311/c-lambda-expression-in-unevaluated-context


1.Lambda expressions are not allowed in unevaluated contexts (such as decltype) before C++20.


2.Closure types are not default constructible before C++20. In C++20 a closure type that has no capture is default constructible.


解决之道


问题原因清楚了,如何解决?不能轻易的就换成c++20的工具链吧。方法还是有的,可以改为仿函数实现。定义一个myGreater仿函数解决:


#include <iostream>
#include <queue>
using Ty = std::pair<std::string,int>;
struct myGreater {
   bool operator() (Ty a, Ty b){
     return a.second > b.second; //大顶堆
  }
};
int main() {
  std::cout << "hello test" << std::endl;
  std::priority_queue<Ty,
        std::vector<Ty>,
        myGreater> q;
  q.emplace(std::make_pair("yang",3));
  q.emplace(std::make_pair("yong",2));
  q.emplace(std::make_pair("zhen",1));
  std::cout << "q.top()=" <<q.top().first <<std::endl;
  return 0;
 }


如果使用std::greater这个默认的比较器会怎样?它先比较pair的第一个元素。如上例假若改为


std::greater<Ty>,输出结果为:"yang",若第一个元素相同则比较第二个元素。不过既然是pair就该使用自定义的比较器。


priority_queue()默认按照从小到大排列,less,最大堆(降序)。所以top()返回的是最大值而不是最小值。


使用greater<>后,数据从大到小排列,最小堆(升序),top()返回的就是最小值而不是最大值。


附,优先级队列的简单模拟实现(最大堆):


// 默认最大堆,less,从大到小排序 最大的堆顶最先pop
#include <iostream>
#include <math.h>
const size_t MAX_NUM = 50;
template <typename T>
class priority_queue{
public:
    T data[MAX_NUM]; // 存储元素的数组
    int count = 0; // 当前队列中的元素个数
public:
    bool empty(){
        return count == 0;
    }
    int top(){
        if (!empty()) {
            return data[0];
        }
        return -1;
    }
    void push(T key){
        data[count] = key;
        swim(count);
        count++;
    }
    void pop(){
        count--;
        exch(0, count);
        data[count] = -1;
        sink(0);
    }
    /* 上浮第k个元素 */
    void swim(int k){
        while ( (k > 0) && less(parent(k), k)) {
            exch(parent(k), k);
            k = parent(k);
        }
    }
    /* 下沉第k个元素 */
    void sink(int k){
        while (left(k) < count) {
            int older = left(k);
            if (right(k) < count && less(older, right(k))) {
                older = right(k);
            }
            if (less(older, k)) break;
            exch(k, older);
            k = older;
        }
    }
    int parent(int child){
        return (child-1)/2;
    }
    int left(int root){
        return root*2 + 1;
    }
    int right(int root){
        return root*2 + 2;
    }
    /* data[i] 是否⽐ data[j] ⼩? */
    bool less(T i, T j){
        return data[i] < data[j];
    }
    /* 交换数组的两个元素 */
    void exch(int i, int j){
        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
    void print(){
        for(int i = 0 ; i < count; i++){
              std::cout << " " << data[i] << std::endl;
        }
    }
};
int main(){
    std::cout << "hello test" << std::endl;
    priority_queue<int> q;
    q.push(12);
    q.push(11); 
    q.push(7); 
    q.push(8);
    q.push(9);
    q.push(3); 
    q.push(2);
    q.push(5); 
    q.push(6);
    q.print();
    std::cout << "q.top:" << q.top() << std::endl;
    q.pop();
    q.print();
    std::cout << "test over" << std::endl;
}


引用


c++ 优先队列(priority_queue)_STATICHIT静砸的博客-CSDN博客_c++ 优先队列


C++简单实现优先队列 - 简书


什么是二叉堆?_韩师学子--小倪的博客-CSDN博客_二叉堆


二叉堆 - 简书


通俗易懂,什么是二叉堆?_WrxDark的博客-CSDN博客_二叉堆


什么是二叉堆_「已注销」的博客-CSDN博客_二叉堆

相关文章
|
11天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
127 75
|
11天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
34 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
11天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
35 9
|
11天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
29 7
|
2月前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
131 3
|
2月前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
123 7
|
2月前
|
消息中间件 存储 安全
|
2月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
49 0
|
3月前
|
存储 算法 调度
【C++打怪之路Lv11】-- stack、queue和优先级队列
【C++打怪之路Lv11】-- stack、queue和优先级队列
53 1
|
4月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
24 0