Bootstrap

C++学习指南(七)——stack/queue/priority_queue

        欢迎来到繁星的CSDN,本期内容主要包括stack(栈),queue(队列),priority_queue(堆)

        

    实际上,C++中的stack、queue还有priority_queue与C语言中的内容无异,所以本篇文章主要想去阐述的,是有关stack、queue还有priority_queue的一些接口与实现原理。 

一、deque

    在说明栈、队列以及优先队列是如何实现之前,我们必须阐述deque的相关知识。

    deque是什么?为什么要有deque?以及deque在模拟实现中扮演着什么样的角色?

 

   当我们翻到stl库里的stack和queue的头文件时(只需要在VS2022所在盘搜索stl_queue.h和stl_stack.h即能得到其头文件,这边推荐使用everything软件辅助快速寻找)我们可以看到Sequence的缺省值是deque<_Tp>,Sequence在后续的代码中不难发现是起容器的作用,而_Tp则扮演了stack内存储值的类型。

      deque是什么以及为什么用deque

        

   deque实际上是vector与list的结合体,由多个vector组成,并以list的形式串连起来。

   这么做的好处是什么?

   让我们回想stack和queue的特点,一个是先进先出,一个是后进先出。如果都用vector来储存的话,那么stack需要的是pop_back(),queue需要的是pop_front()。但是用vector来pop_back简单,pop_front就难上加难了,用list的话首尾删除插入倒是方便,但是在存储空间上由于存储的地址不连续性,以及list每个结点所需要存储的信息更多,使得整体耗费的存储空间更大,且首插尾插效率很低。

   于是deque,一个结合了vector与list的东西诞生了。

   很可惜的是,事实上deque结合了vector和list的优点,更结合了vector与list的缺点。进行尾删操作尚且简单,如果进行头删则需要将其他位置所有的数据向前搬一格。如果进行插入操作,则每次插入都有可能多一个同样长度的vector,一插一删之间,deque很可能造成大量的空间浪费,亦或是牺牲了“搬迁”的效率。

    或许在平均效率上deque比单纯用vector或者list更高吧,但stl创建者选用deque的原因我们不得而知。

二、stack和queue的接口

        stack和queue的接口是十分类似的。

push(x);
pop();
empty();
size();
top();//stack
front();//queue
back();//queue

        与string的区别是,string是push_back,但stack和queue是push。string是pop_back,但stack和queue是pop。queue的front和back分别代表第一个元素以及最新被push的元素。stack的top表示栈顶,没有栈底接口。

     为什么要坚持用接口?

       这一点实际上在封装的那一部分已经被提到过了。我们用接口的目的一方面是为了更简单地使用这些复杂的数据结构,一方面是为了数据的安全性,我们只能使用接口对这些结构进行改造,而不能直接从底层的vector或者deque改数据,这种操作对于结构而言是“不合法”的(对于程序而言并没有什么问题)。

三、priority_queue

       优先队列(priority_queue)实际上是堆,其优先性区分了大根堆与小根堆,但值得注意的是,如果需要使用priority_queue,我们需要包的头文件是<queue>。

       template<class T,class Container = std::vector<T>,class Compare = std::less<typename Container::value_type>> class priority_queue; 

        默认的优先队列是大根堆,在日常使用中我们仅仅需要输入堆所存储的数据类型即可。如果需要小根堆,那么就需要输入数据类型,存储容器(一般为vector),以及std::greater<>(递增)。或者自己书写仿函数。

        优先队列的常用接口如下:

push(x);
pop();
top();
empty();
size();

        其中push放入后,优先队列会自动进行排序,top即堆的根,pop即弹出堆的根,size和empty就不过多赘述了。

        下面是大致的模拟实现。

        

#pragma once
#include<deque>
template<class T,class Con = std::deque<T>>
class stack
{
public:
    stack();
    void push(const T& x) {
        _c.push_back(x);
    }
    void pop() {
        _c.pop_back();
    }
    T& top() {
        return _c.back();
    }
    const T& top()const {
        return _c.back();
    }
    size_t size()const {
        return _c.back();
    }
    bool empty()const {
        return _c.empty();
    }
private:
    Con _c;
};
#pragma once
#include<deque>
template<class T, class Con = std::deque<T>>

class queue
{
public:
    queue();
    void push(const T& x) {
        _c.push_back(x);
    }
    void pop() {
        _c.pop_front();
    }
    T& back() {
        return _c.back();
    }
    const T& back()const {
        return _c.back();
    }
    T& front() {
        return _c.front();
    }
    const T& front()const {
        return _c.front();
    }
    size_t size()const {
        return _c.size();
    }
    bool empty()const {
        return _c.empty();
    }
private:
    Con _c;
};
#pragma once
namespace bit
{
#include<vector>
#include<functional>
    template <class T, class Container = std::vector<T>, class Compare = std::less<T> >
    class priority_queue
    {
    public:
        priority_queue();
        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last) {
            std::make_heap(first,last,comp);
        }
        bool empty() const {
            return c.empty();
        }
        size_t size() const {
            return c.size();
        }
        T& top() const {
            return c.front();
        }
        void push(const T& x) {
            x.push_back(x);
            std::push_heap(c.begin(), c.end(), comp);
        }
        void pop() {
            std::pop_heap(c.begin(), c.end(), comp);
            c.pop_back();
        }
    private:
        Container c;
        Compare comp;
    };
};

;