【C++】C++ STL探索:Priority Queue与仿函数的深入解析(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【C++】C++ STL探索:Priority Queue与仿函数的深入解析

一、优先级队列

1.1 优先级队列介绍

[优先级队列文档介绍](priority_queue - C++ Reference (cplusplus.com))

  1. 优先队列是一个容器适配器,根据严格的弱排序标准,它的第一个元素总是它所含的元素中最大的
  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)
  3. 优先级队列已经不能称为队列,不符合FIFO特性拉
  4. 优先队列被实现为容器适配器,容器适配器即将特定的容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素,元素从特定容器的"尾部"弹出,其称为优先队列的顶部
  5. 底层容器可以是任何标准容器类模板,也可以是特定设计的容器类,容器应该可以通过随机访问迭代器访问,并支持以下操作
  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素
  1. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector
  2. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_head,push_heap和pop_heap来自动完成此操作。

1.2 优先级队列使用

优先级队列属于容器适配器,并跟堆具有是否相似的结构与功能,这里可以看成堆。由于堆是通过完全二叉树进行搭建,优先级队列默认使用vector作为其底层存储数据的容器,调用库中priority_queue类使用头文件

默认情况下priority_queue是大堆

#pragma once
#include <vector>
#include <algorithm>
namespace bit
{
  template<class T>
  class Less
  {
  public:
    bool operator()(const T& x, const T& y)
    {
      return x < y;
    }
  };
  template<class T>
  class Greater
  {
  public:
    bool operator()(const T& x, const T& y)
    {
      return x > y;
    }
  };
  template<class T, class Containor = vector<T>>
  class priority_queue
  {
  public:
    void push(const T& x)
    {
      _con.push_back(x);
      adjust_up(_con.size()-1);
    }
    void adjust_up(size_t child)
    {
      size_t parent = (child - 1) / 2;
      while (child > 0)
      {
        if (_con[parent] < _con[child])
        {
          std::swap(_con[child], _con[parent]);
          child = parent;
          parent = (child - 1) / 2;
        }
        else
          break;
      }
    }
    void pop()
    {
      std::swap(_con[0], _con[_con.size()-1]);
      _con.pop_back();
      adjust_down(0);
    }
    void adjust_down(size_t parent)
    {
      size_t child = parent * 2 + 1;
      while (child  < _con.size())
      {
        if (child + 1 < _con.size() && _con[child] < _con[child + 1]) child++;
        if (_con[parent] < _con[child])
        {
          std::swap(_con[child], _con[parent]);
          parent = child;
          child = parent * 2 + 1;
        }
        else
          break;
      }
    }
    
    const T& top()
    {
      return _con[0];
    }
    size_t size()
    {
      return _con.size();
    }
    bool empty()
    {
      return _con.empty();
    }
  private:
    Containor _con;
  };
  void test()
  {
    priority_queue<int> pq;
    pq.push(3);
    pq.push(2);
    pq.push(2);
    pq.push(110);
    while (!pq.empty())
    {
      cout << pq.top() << " ";
      pq.pop();
        }
    cout << endl;
  }
}

以上就是建堆的逻辑,但是如果需要建小堆,需要去内部修改大于小于号,显得有些繁琐。对此引用了一个类模板适应不同类型,对operator()函数进行运算符重载,作为控制比较逻辑,其中在外部进行开关的控制。

问题:

  • 如果需要比较的逻辑,C语言中的sqort函数不行吗?

答:

  • C++不喜欢使用qsort函数,参数部分的函数指针显得很麻烦,C++喜欢使用类模板中的仿函数。

1.3 小补充:priority_queue类提供的仿函数

关于仿函数,库中已经实现好了greater和less仿函数可以直接使用,priority_queue默认使用的是less建大堆

二、仿函数

2.1 仿函数概念

仿函数是通过类中运算符重载()实现特定的功能,通过使用匿名对象使用,它的对象可以想函数一样去使用,使得很难区分是调用函数还是调用匿名对象

Less lessfunc;
cout << lessfunc(1, 2) << endl;
cout <<  lessfunc.operator()(1, 2) << endl;
cout <<  Less()(1, 2) << endl;

这里推荐使用第二种和第三种,为了提高代码的可读性,第一种写法可能会引起歧义。

2.2 仿函数访问限定符

template<class T>
    class Less
    {
        public:
        bool operator()(const T& x, const T& y)
        {
            return x < y;
        }
    };
template<class T>
    class Greater
    {
        public:
        bool operator()(const T& x, const T& y)
        {
            return x > y;
        }
    };

仿函数中数据是需要公开使用,对于可以通过strcut或将class访问权限设置为public。仿函数/函数对象可以像函数一样去使用,**但是本质是类调用运算符重载。**那么为什么这么麻烦呢?直接写个函数不就行吗?还仿函数,听起来很牛逼哄哄呀!


【C++】C++ STL探索:Priority Queue与仿函数的深入解析(二)https://developer.aliyun.com/article/1617383

相关文章
|
11天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
27 7
|
29天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
54 4
|
1月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
72 5
|
1月前
|
自然语言处理 编译器 Linux
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
设计模式 安全 数据库连接
【C++11】包装器:深入解析与实现技巧
本文深入探讨了C++中包装器的定义、实现方式及其应用。包装器通过封装底层细节,提供更简洁、易用的接口,常用于资源管理、接口封装和类型安全。文章详细介绍了使用RAII、智能指针、模板等技术实现包装器的方法,并通过多个案例分析展示了其在实际开发中的应用。最后,讨论了性能优化策略,帮助开发者编写高效、可靠的C++代码。
38 2
|
10天前
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
16 0
|
10天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
40 0
|
1月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
52 0
|
14天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
27 0

推荐镜像

更多