【C++笔记】STL详解: priority_queue 的使用

前言:

            priority_queue (优先队列) 是计算机科学中非常常用且强大的数据结构,尤其在 C++ 标准模板库 (STL) 中被广泛使用。

            它属于“容器适配器”,这意味着它不是从零开始构建的,而是封装了其他底层容器(例如 vector),只提供特定的接口来限制元素的访问方式。

        

一、前置知识

        

1.1 仿函数的简介

        

仿函数 (Functor),也被称为函数对象 (Function Object)。

        

用一句话来概括:它本质上是一个“类 (Class)”或“结构体 (Struct)”,但你可以像调用“普通函数”一样去调用它的对象。

        

        

1.2 仿函数的核心原理

        

一个对象之所以能像函数一样被调用,是因为这个 “类 (Class)”  或 “结构体 (Struct)” 重载了函数调用运算符。         

        

A. 仿函数的基本长相

        

代码示例:要创建一个仿函数,你只需要定义一个类(或结构体),并在其中实现 operator() 方法。

#include <iostream>

// 定义一个仿函数类
struct Multiplier
{
    // 重载 operator()
    int operator()(int a, int b) const
    {
        return a * b;
    }
};

int main()
{
    Multiplier multiply; // 实例化一个对象

    // 像调用函数一样使用这个对象
    int result = multiply(3, 4);

    std::cout << "Result: " << result << std::endl; // 输出 12
    return 0;
}

        

B. 普通函数与仿函数

        

普通函数与仿函数的调用很类似,具体样式如下:        

        

普通函数调用:

void printHello()
{
    std::cout << "Hello!" << std::endl;
}

int main()
{
    // 调用
    printHello();
    return 0;
}

      

仿函数调用:

struct Functor 
{
    // 重载 () 运算符
    void operator() () 
    {
        std::cout << "Hello from Functor!" << std::endl;
    }
};

int main()
{
    // 调用
    Functor myPrinter; // 1. 先创建一个对象
    myPrinter();                 // 2. 像函数一样调用这个对象!
}

        

       

1.3 仿函数的优势

        

A. 仿函数可以“拥有状态”

        

普通函数在执行完毕后,局部变量就销毁了,如果想让普通函数记住某些状态,通常只能用全局变量或静态局部变量。

        

仿函数是一个类,它可以有成员变量,从而能够在多次调用之间保存和传递状态。

        

代码如下所示:

struct Adder 
{
    int base_value; // 状态:基础值

    // 构造函数初始化状态
    Adder(int val) : base_value(val) {}

    int operator()(int x) const
    {
        return x + base_value;
    }
};

int main() 
{
    Adder add_five(5);  // 创建一个“总是加5”的仿函数
    Adder add_ten(10);  // 创建一个“总是加10”的仿函数

    std::cout << add_five(10) << std::endl; // 输出 15
    std::cout << add_ten(10) << std::endl;  // 输出 20
}

        

B. 在 STL 模板中性能更好,且方便作为类型传递

        

像 priority_queue、std::sort 等 C++ 标准库组件,大量依赖模板。

        

如果你传一个普通函数指针给 std::sort,编译器很难对它进行“内联优化 (Inline)”,每次比较都会有函数调用的开销。

        

如果你传一个仿函数的类型,编译器在编译时就能确切知道你要调用的是哪一段代码,从而直接将代码内联展开,运行速度更快。

        

经典应用场景:结合 STL 使用

        

代码示例:假设我们要对一个整数数组进行降序排序

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 定义一个用于降序比较的仿函数
struct DescendingOrder
{
    bool operator()(int a, int b) const
    {
        return a < b; 
    }
};

void bubbleSort(vector<int>& v,DescendingOrder Less)
{
    for (int i =0; i<v.size()-1;i++)
    {
        for (int j = 0; j <v.size()-1; j++)
        {
            //传统写法
            //if (v[j] < v[j + 1])
            //{
            //    swap(v[j], v[j + 1]);
            //}

            //利用仿函数
            if (Less(v[j], v[j + 1]))
            {
                swap(v[j], v[j + 1]);
            }

       }
    }
}

int main()
{
    vector<int> nums = { 3, 1, 4, 1, 5, 9 };

    DescendingOrder Less;
    bubbleSort(nums,Less);

    for (int n : nums)
    {
        cout << n << " "; // 输出: 9 5 4 3 1 1
    }
}

        

        

二、priority_queue

        

2.1 priority_queue的介绍

        

在 C++ 的STL 中,priority_queue 默认是基于堆 (Heap) 这种数据结构来实现的,通常是一个大顶堆。

        

1. 定义在头文件 <queue>

        

2. 底层:基于数据结构中的堆

        

3. 核心特性:优先队列中的元素被赋予了优先级,无论元素是以什么顺序进入队列的,每次被弹出(取出)的永远是优先级最高的那个元素。

        

2.2 priority_queue的定义方式

        

priority_queue 的类模板声明

        

template <class T ,  class Container = vector<T> , class Compare = less<T> > class priority_queue;

        

1. class T    

        

解释:T 代表这个栈里要装的数据类型。

        

例如:当你写 priority_queue<int> 时,编译器就把这里的 T 全部替换成 int,如果你写 priority_queue<std::string>,T 就是字符串类型。

   

    

2.class Container = vector<T>

        

解释:

        

① class Container:这指明了底层具体是用什么数据结构来存数据的。

        

priority_queue 本身不直接管理内存,它是一个“包工头”,把具体存取数据的工作外包给了 Container。

        

        

② = vector<T>  (默认模板参数):如果在定义priority_queue时候,你只提供存什么数据(T),

        

而没指定怎么存(Container),编译器就会默认使用 std::vector<T>作为底层的存储结构。
    

        

3. class Compare = less<T>        

解释:

        

① class Compare: 它是一个函数对象(仿函数/Functor),用于比较两个元素的大小       

        

② = less<T> (默认模板参数)  :less<T> 是 C++ 标准库(包含在 <functional> 头文件中)提供的一个内置仿函数

        

简单来说:它就是一个系统帮你写好的、专门用来判断“左边是否小于右边 (<)”的工具。

        

        

注:

        

反直觉的重点: less<T> 的字面意思是“小于”,但它在优先队列中的作用是把最大的元素放在队首,也就是形成一个大顶堆 (Max-Heap)。

                

核心原因:less(父节点, 新节点),意思是:父节点 < 新节点,父节点得给新节点让位(下沉),

                

                  让大的新节点上浮,导致最大的节点一路畅通无阻地浮到了最顶端(根节点),从而形成了大顶堆。

                

        

改为小顶堆: 如果你想让最小的元素优先级最高(放在队首),需要将其替换为 std::greater<T>。

                

核心原因:greater(父节点, 新节点),意思是 父节点 > 新节点,父节点得给新节点让位(下沉)

        

                 让小的新节点上浮,导致最小的节点一路畅通无阻地浮到了最顶端(根节点),从而形成了小顶堆。

        

        

场景 1:基础数据类型的“大顶堆”

        

我们要存储 int,并且希望数字越大,优先级越高,则使用默认的“大顶堆”

#include <iostream>
#include <queue>
#include <vector>
using namespace std;


int main()
{
    // 1. T 是 int
    // 2. Container 是 vector<int>
    // 3. Compare 不写默认为大顶堆
    priority_queue<int, vector<int>> max_pq;

    max_pq.push(5);
    max_pq.push(1);
    max_pq.push(9);
    
    while (!max_pq.empty())
    {
        cout <<"当前堆顶为:" << max_pq.top() << endl;
        max_pq.pop();
    }

    return 0;
}

        

        

场景 2:基础数据类型的“大顶堆”

        

我们要存储 int,并且希望数字越小,优先级越高,此时必须写全三个参数:

#include <iostream>
#include <queue>
#include <vector>
using namespace std;


int main()
{
    // 1. T 是 int
    // 2. Container 是 vector<int>
    // 3. Compare 是 greater<int> (小顶堆)
    priority_queue<int, vector<int>, greater<int>> min_pq;

    min_pq.push(5);
    min_pq.push(1);
    min_pq.push(9);
    
    while (!min_pq.empty())
    {
        cout <<"当前堆顶为:" << min_pq.top() << endl;
        min_pq.pop();
    }

    return 0;
}

        

场景 3:存储自定义结构体 (Struct)

        

如果我们队列里存的是自定义的结构体(比如:游戏中的怪物,按照威胁等级排序),我们需要自己告诉 priority_queue 如何比较它们。

        

方法 A:重载 < 运算符 (最常用)  如果在结构体内部重载了 <,就可以直接使用默认的 less<T>,只传第一个参数即可。

        

代码示例:

#include <iostream>
#include <queue>
using namespace std;

struct Monster 
{
    string name;
    int threatLevel;

    // 重载 < 运算符
    // 注意:如果是 less,想让 threatLevel 大的在前面,就按照正常的 < 逻辑写即可
    bool operator<(const Monster& other) const 
    {
        return this->threatLevel < other.threatLevel;
    }
};

int main() 
{
    // 只需提供类型,使用默认的 vector 和 less
    // 1. T 是 struct Monster 
    // 2. Container 是 vector<Monster>
    // 3. Compare 是 greater<int> (小顶堆)
    priority_queue<Monster> pq;

    pq.push({ "Goblin", 10 });
    pq.push({ "Dragon", 99 });
    pq.push({ "Slime", 1 });

    cout << "威胁等级排名第一: " << pq.top().name << endl; // 输出: 先打: Dragon
    return 0;
}

        

方法 B:自定义仿函数 (Functor) 如果你不想修改结构体本身的代码,可以利用第三个模板参数 Compare,写一个自定义的比较类。

        

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

struct Node
{
    int x, y, cost;
};

// 自定义比较类
struct CompareNode 
{
    bool operator()(const Node& a, const Node& b)
    {
        // 如果想让 cost 小的排在前面(小顶堆),就用 > 
        return a.cost > b.cost;
    }

};

int main() 
{
    // 只需提供类型,使用默认的 vector 和 less
    // 1. T 是 struct Node 
    // 2. Container 是 vector<Node>
    // 3. Compare 是 CompareNode (小顶堆)

    // 必须写全三个参数,第三个参数传入我们自定义的 CompareNode
   priority_queue<Node, vector<Node>, CompareNode> pq;

    pq.push({ 0, 0, 50 });
    pq.push({ 1, 2, 10 });
    pq.push({ 3, 3, 20 });

    cout << "最低消耗: " << pq.top().cost <<endl; // 输出: 最低消耗: 10
    return 0;
}

        

2.3 priority_queue 的常用函数

        

2.3.1 push

        

模板参数:template<class T>        

        

函数原型:void push (const T& val);

        

函数功能:在priority_queue 中插入一个新元素,并维持堆的性质。

        

代码示例:

// priority_queue::push/pop
#include <iostream>       // std::cout
#include <queue>          // std::priority_queue

int main()
{
    //默认为大根堆
    std::priority_queue<int> mypq;

    mypq.push(30);
    mypq.push(100);
    mypq.push(25);
    mypq.push(40);

    std::cout << "Popping out elements..."<<std::endl;
    while (!mypq.empty())
    {
        std::cout<< mypq.top() <<" ";
        mypq.pop();
    }
    std::cout << '\n';

    return 0;
}

        

输出结果:

        

        

2.3.2 pop

        

函数原型:void pop();

        

函数功能:移除堆顶元素,并维持堆的性质。

        

代码示例:

// priority_queue::push/pop
#include <iostream>       // std::cout
#include <queue>          // std::priority_queue

int main()
{
    //默认为大根堆
    std::priority_queue<int> mypq;

    mypq.push(30);
    mypq.push(100);
    mypq.push(25);
    mypq.push(40);

    std::cout << "Popping out elements..."<<std::endl;
    while (!mypq.empty())
    {
        std::cout<< mypq.top() <<" ";
        mypq.pop();
    }
    std::cout << '\n';

    return 0;
}

        

输出结果:

        

        

2.3.3 top

        

模板参数:template<class T>         

        

函数原型: 

               

①T & top();

        

②const T& top() const;

        

函数功能:返回对priority_queue 中顶部元素的常量引用。

        

代码示例:

// priority_queue::top
#include <iostream>       // std::cout
#include <queue>          // std::priority_queue

int main()
{
	std::priority_queue<int> mypq;

	mypq.push(10);
	mypq.push(20);
	mypq.push(15);

	std::cout << "mypq.top() is now " << mypq.top() << '\n';

	return 0;
}

        

输出结果如下:

        

        

2.3.4 empty

        

函数原型:bool empty() const;

        

函数功能:判断 priority_queue 是否为空

        

代码示例:

// priority_queue::empty
#include <iostream>       // std::cout
#include <queue>          // std::priority_queue

int main ()
{
  std::priority_queue<int> mypq;
  int sum = 0;

  for (int i=1;i<=10;i++) mypq.push(i);

  while (!mypq.empty())
  {
     sum += mypq.top();
     mypq.pop();
  }

  std::cout << "total: " << sum << '\n';

  return 0;
}

        

输出结果为:

        

        

2.3.5 size

        

函数原型:  size_type size() const;

        

函数功能:返回 priority_queue 中的元素数量。

        

代码示例:

// priority_queue::size
#include <iostream>       // std::cout
#include <queue>          // std::priority_queue

int main()
{
	std::priority_queue<int> myints;
	std::cout << "0. size: " << myints.size() << '\n';

	for (int i = 0; i < 5; i++) myints.push(i);
	std::cout << "1. size: " << myints.size() << '\n';

	myints.pop();
	std::cout << "2. size: " << myints.size() << '\n';

	return 0;
}

        

输出结果如下:

        

                

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值