【C++打怪之路Lv11】-- stack、queue和优先级队列

简介: 【C++打怪之路Lv11】-- stack、queue和优先级队列

C++为什么要学习stack、queue和优先级队列


学习C++中的栈(stack)、队列(queue)和优先级队列(priority queue)对开发者来说非常重要,因为它们是常用的数据结构,在解决各种编程问题时提供了高效的方法。

以下是它们的核心作用和特点:

1. 栈(Stack):

  特点:后进先出(LIFO),即最后入栈的元素最先出栈。

  应用:适用于函数调用、表达式求值、撤销操作等场景。例如,程序在执行递归调用时会用栈来存储函数状态。

2. 队列(Queue):

  特点:先进先出(FIFO),即最先进入队列的元素最先被处理。

  应用:适用于任务调度、消息处理、广度优先搜索等需要按顺序处理数据的场景。

3. 优先级队列(Priority Queue):

  特点:元素按优先级排序,每次取出的是优先级最高的元素,而不是按插入顺序。

  应用:适用于任务管理(例如操作系统中的任务调度)、最短路径算法(如Dijkstra算法)、数据流中找出Top K元素等场景。


这些数据结构在C++中有标准库支持(如`<stack>`、`<queue>`和`<priority_queue>`),可以帮助开发者更高效地处理特定类型的问题,提高程序的可读性和性能。


stack类


stack类的介绍

stack 是 C++ 标准模板库(STL)中的一个容器适配器,用于实现后进先出(LIFO)数据结构。

基于其他序列容器(如 dequevectorlist)构建,默认使用 deque 作为底层容

stack<int, vector<int>> st;

注:

1、定义了一个名为 st 的栈(stack)

其中包含 int 类型的元素,并使用 vector<int> 作为其底层容器

2、stack 默认使用 deque 作为底层容器,但可以通过这种方式替换成其他容器

3、特点 & 好处

  • 该栈 st 仍然保持 stack 的 后进先出 特性。
  • 使用 vector 作为底层容器意味着栈的元素实际上存储在一个 vector 中,但只能通过 stack 提供的接口(如 push()pop()top() 等)访问。

这样做的优势在于,如果对底层容器的特性(如内存管理或其他特定操作)有特殊需求,可以灵活选择适合的容器

stack的基本操作

  • push()将元素压入栈顶
  • pop()移除栈顶元素
  • top():返回栈顶元素的引用只能访问栈顶元素(通过 top()),无法直接访问其他位置的元素
  • empty():判断栈是否为空
  • size():返回栈中元素的个数

stack的模拟实现

namespace sky
{
  template<class T, class Container = vector<int>>
  class Stack
  {
  public:
    void push(const T& val)
    {
      _con.push_back(val);  // 压栈插入数据
    }
 
    void pop()
    {
      _con.pop_back();    // 删除用尾删
    }
 
    const T& top()
    {
      return _con.back();   // 后进先出
    }
 
    bool empty()
    {
      return _con.empty();
    }
 
    size_t size()
    {
      return _con.size();
    }
  private:
    Container _con;
  };
}
 
int main()
{
  sky::Stack<int,vector<int>> st;
  st.push(1);
  st.push(2);
  st.push(3);
  st.push(4);
  st.push(5);
 
  cout << st.size() << endl;
 
  while (!st.empty())
  {
    cout << st.top() << " ";
    st.pop();
  }
  cout << endl;
 
  return 0;
}


queue类


queue类的介绍

1、queue 是标准库容器适配器,用于实现先进先出(FIFO)队列。

它基于底层容器(如 dequelist)进行封装

2、默认使用 deque 作为存储容器,也可以指定其他符合双端操作要求的容器。

queue 适用于需要按顺序处理数据的场景,如任务调度或广度优先搜索等

queue的基本操作

  • push():向队尾添加元素
  • pop()移除队头元素
  • front():返回队头元素的引用
  • back():返回队尾元素的引用
  • empty()检查队列是否为空
  • size():返回队列中元素的数量

queue的模拟实现

namespace sky
{
  template<class T, class Container = list<int>>
  class Queue
  {
  public:
    void push(const T& val)
    {
      _con.push_back(val);
    }
 
    void pop()
    {
      _con.pop_front();
    }
 
    T& front()
    {
      return _con.front();
    }
 
    T& back()
    {
      return _con.back();
    }
 
    bool empty()
    {
      return _con.empty();
    }
 
    size_t size()
    {
      return _con.size();
    }
  private:
    Container _con;
  };
}
 
int main()
{
  sky::Queue<int> q;
  q.push(1);
  q.push(2);
  q.push(3);
  q.push(4);
 
  cout << q.back() << endl;
 
  cout << q.size() << endl;
  while (!q.empty())
  {
    cout << q.front() << " ";
    q.pop();
  } 
 
  return 0;
}


优先级队列


优先级队列的介绍

priority_queue 是标准库容器适配器,用于实现优先级队列,其元素根据优先级自动排序。默认情况下,它使用最大堆结构,使得队头元素总是优先级最高的元素

priority_queue 适用于需要动态维护最大(或最小)元素的场景,如任务调度、路径规划等

注:需要包头文件<queue>


仿函数


什么是仿函数

priority_queue的函数原型比queuestack多了一个参数,用于指定比较函数,这个参数决定了元素在priority_queue中的排序方式

比较函数(仿函数):priority_queue需要一个比较函数(通常是仿函数)来定义元素之间的优先级顺序。默认情况下,它使用std::less<T>,这意味着它将按照从小到大的顺序排列,形成一个最大堆

这是priority_queue与queue和stack最显著的区别,因为priority_queue需要根据元素的优先级来组织数据,而queue和stack仅需要遵循基本的先进先出或后进先出原则

为什么要有仿函数

  • 自定义比较逻辑:优先级队列默认情况下是一个最大堆,即最大的元素具有最高的优先级。但有时我们可能需要根据不同的标准来排序元素,比如最小堆或者基于对象的某个成员函数。通过使用仿函数,我们可以自定义比较逻辑,使得优先级队列能够按照我们期望的顺序排列元素。
  • 状态保持:仿函数可以保持状态,这意味着它们可以在调用之间保留信息,这在某些复杂的比较逻辑中非常有用。
  • 类型安全:使用仿函数可以保证类型安全,因为编译器会在编译时检查传递给仿函数的参数类型是否正确。

优先级队列的使用

成员函数

  • push()插入元素,并自动调整位置
  • pop()移除优先级最高的元素(队头)
  • top()访问优先级最高的元素
  • empty():检查队列是否为空。
  • size():返回元素的数量

排序准则

默认使用 std::less(大顶堆),可以通过自定义比较函数来实现小顶堆或其他排序

优先级队列的模拟实现

namespace sky
{
  template<class T>
  class less
  {
  public:
    bool operator()(const T& x, const T& y)
    {
      // 在构建堆时,x < y 的意思是:如果 x 比 y 小,
      // 那么 x 的优先级低于 y,所以 y 应该排在 x 前面
      return x < y;
    }
  };
 
  template<class T>
  class greater
  {
  public:
    // 当使用 greater 作为比较器时,较小的元素会排在前面,因此可以创建一个"最小优先队列"
    bool operator()(const T& x, const T& y)
    {
      return x > y;
    }
  };
 
  template<class T, class Container = vector<int>, class Compare = less<T>>
  class priority_queue
  {
  public:
    void adjust_up(int child)
    {
      Compare com;
      int parent = (child - 1) / 2;
      while (child > 0)
      {
        // 如果父节点的值比子节点的值小,则交换(基于比较器的定义)
        if (com(_con[parent], _con[child]))
        {
          swap(_con[parent], _con[child]);
          child = parent;
          parent = (child - 1) / 2;
        }
        else
        {
          break;
        }
      }
    }
 
    void adjust_down(int parent)
    {
      Compare com;
      int child = parent * 2 + 1;
      while (child < _con.size())
      {
        // 如果有右子节点且右子节点比左子节点大,则选择右子节点
        if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
        {
          child++;
        }
        // 如果父节点比选定的子节点小,则交换
        if (com(_con[parent], _con[child]))
        {
          swap(_con[parent], _con[child]);
          parent = child;
          child = parent * 2 + 1;
        }
        else
        {
          break;
        }
      }
    }
 
    // 插入元素并调整堆
    void push(const T& val)
    {
       _con.push_back(val);
       adjust_up(_con.size() - 1);
    }
 
    // 删除堆顶元素并调整堆
    void pop()
    {
      if (!_con.empty())
      {
        swap(_con[0], _con[_con.size() - 1]);
        _con.pop_back();
        adjust_down(0);
      }
    }
 
    const T& top() const
    {
      return _con[0];
    }
 
    bool empty() const
    {
      return _con.empty();
    }
 
    size_t size() const
    {
      return _con.size();
    }
  private:
    Container _con;
  };
}
 
int main()
{
  sky::priority_queue<int> pq;
  pq.push(3);
  pq.push(1);
  pq.push(5);
  pq.push(4);
  pq.push(2);
 
  while (!pq.empty())
  {
    cout << pq.top() << " ";
    pq.pop();
  }
  cout << endl;
 
  return 0;
}


关于容器适配器


目录
打赏
0
1
1
0
17
分享
相关文章
|
2月前
|
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
74 21
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
39 16
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
20天前
|
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
65 6
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。