STL——stack和queue(priority_queue)

stack的介绍

stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其只能从容器的一端进行元素的插入与提取操作。image.png

stack的定义方式

方式一: 使用默认的适配器定义栈。

stack st1;

方式二: 使用特定的适配器定义栈。

stack<int, vector> st2;
stack<int, list> st3;

注意: 如果没有为stack指定特定的底层容器,默认情况下使用deque。

stack的使用

stack当中常用的成员函数如下:

成员函数功能
empty判断栈是否为空
size获取栈中有效元素个数
top获取栈顶元素
push元素入栈
pop元素出栈
swap交换两个栈中的数据
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main()
{
    stack<int, vector<int>> st;
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
    cout << st.size() << endl; //4
    while (!st.empty())
    {
        cout << st.top() << " ";
        st.pop();
    }
    cout << endl; //4 3 2 1
    return 0;
}

queue的介绍

队列是一种容器适配器,专门用在具有先进先出操作的上下文环境中,其只能从容器的一端插入元素,另一端提取元素。
image.png

queue的定义方式

方式一: 使用默认的适配器定义队列。

queue q1;

方式二: 使用特定的适配器定义队列。

queue<int, vector> q2;
queue<int, list> q3;

注意: 如果没有为queue指定特定的底层容器,默认情况下使用deque。

queue的使用

queue当中常用的成员函数如下:

成员函数功能
empty判断队列是否为空
size获取队列中有效元素个数
front获取队头元素
back获取队尾元素
push队尾入队列
pop队头出队列
swap交换两个队列中的数据
#include <iostream>
#include <list>
#include <queue>
using namespace std;

int main()
{
    queue<int, list<int>> q;
    q.push(1);
    q.push(2);
    q.push(3);
    q.push(4);
    cout << q.size() << endl; //4
    while (!q.empty())
    {
        cout << q.front() << " ";
        q.pop();
    }
    cout << endl; //1 2 3 4
    return 0;
}


容器适配器

stack和queue有一点需要注意的是,虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和queue只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque容器。
在stack和queue的类模板声明当中我们就可以看到,它们的模板参数有两个,第一个是stack和queue当中所存储的元素类型,而另一个就是指定使用的容器类型。只不过当我们不指定使用何种容器的情况下,stack和queue都默认使用deque作为指定容器。
image.pngimage.png
简单理解: 学过数据结构后我们都知道,stack和queue既可以使用顺序表实现,也可以使用链表实现。在这里我们若是定义一个stack,并指定使用vector容器,则定义出来的stack实际上就是对vector容器进行了包装

stack的模拟实现

知道了容器适配器后,stack的模拟实现就显得相当简单,我们只需要调用所指定容器的各个成员函数即可实现stack的各个函数接口。

成员函数函数作用实现方法
push元素入栈调用所指定容器的push_back
pop元素出栈调用所指定容器的pop_back
top获取栈顶元素调用所指定容器的back
size获取栈中有效元素个数调用所指定容器的size
empty判断栈是否为空调用所指定容器的empty
swap交换两个栈中的数据调用所指定容器的swap
namespace cl //防止命名冲突
{
    template<class T, class Container = std::deque<T>>
    class stack
    {
    public:
        //元素入栈
        void push(const T& x)
        {
            _con.push_back(x);
        }
        //元素出栈
        void pop()
        {
            _con.pop_back();
        }
        //获取栈顶元素
        T& top()
        {
            return _con.back();
        }
        const T& top() const
        {
            return _con.back();
        }
        //获取栈中有效元素个数
        size_t size() const
        {
            return _con.size();
        }
        //判断栈是否为空
        bool empty() const
        {
            return _con.empty();
        }
        //交换两个栈中的数据
        void swap(stack<T, Container>& st)
        {
            _con.swap(st._con);
        }
    private:
        Container _con;
    };
}

queue的模拟实现

同样的方式,我们也是通过调用所指定容器的各个成员函数来实现queue的。

成员函数函数作用实现方法
push队尾入队列调用所指定容器的push_back
pop队头出队列调用所指定容器的pop_front
front获取队头元素调用所指定容器的front
back获取队尾元素调用所指定容器的back
size获取队列中有效元素个数调用所指定容器的size
empty判断队列是否为空调用所指定容器的empty
swap交换两个队列中的数据调用所指定容器的swap
namespace cl //防止命名冲突
{
    template<class T, class Container = std::deque<T>>
    class queue
    {
    public:
        //队尾入队列
        void push(const T& x)
        {
            _con.push_back(x);
        }
        //队头出队列
        void pop()
        {
            _con.pop_front();
        }
        //获取队头元素
        T& front()
        {
            return _con.front();
        }
        const T& front() const
        {
            return _con.front();
        }
        //获取队尾元素
        T& back()
        {
            return _con.back();
        }
        const T& back() const
        {
            return _con.back();
        }
        //获取队列中有效元素个数
        size_t size() const
        {
            return _con.size();
        }
        //判断队列是否为空
        bool empty() const
        {
            return _con.empty();
        }
        //交换两个队列中的数据
        void swap(queue<T, Container>& q)
        {
            _con.swap(q._con);
        }
    private:
        Container _con;
    };
}

priority_queue的介绍

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

注意: 默认情况下priority_queue是大堆。

priority_queue的定义方式

方式一: 使用vector作为底层容器,内部构造大堆结构。

priority_queue<int, vector, less> q1;

方式二: 使用vector作为底层容器,内部构造小堆结构。

priority_queue<int, vector, greater> q2;

方式三: 不指定底层容器和内部需要构造的堆结构。

priority_queue q;

注意: 此时默认使用vector作为底层容器,内部默认构造大堆结构。

priority_queue各个接口的使用

priority_queue的各个成员函数及其功能如下:

成员函数功能
push插入元素到队尾(并排序)
pop弹出队头元素(堆顶元素)
top访问队头元素(堆顶元素)
size获取队列中有效元素个数
empty判断队列是否为空
swap交换两个队列的内容
#include <iostream>
#include <functional>
#include <queue>
using namespace std;
int main()
{
    priority_queue<int> q;
    q.push(3);
    q.push(6);
    q.push(0);
    q.push(2);
    q.push(9);
    q.push(8);
    q.push(1);
    while (!q.empty())
    {
        cout << q.top() << " ";
        q.pop();
    }
    cout << endl; //9 8 6 3 2 1 0
    return 0;
}

priority_queue的模拟实现

priority_queue的底层实际上就是堆结构,实现priority_queue之前,我们先认识两个重要的堆算法。(下面这两种算法我们均以大堆为例)

堆的向上调整算法

image.png
以大堆为例,堆的向上调整算法就是在大堆的末尾插入一个数据后,经过一系列的调整,使其仍然是一个大堆。

调整的基本思想如下:
1、将目标结点与其父结点进行比较。
2、若目标结点的值比父结点的值大,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整;若目标结点的值比其父结点的值小,则停止向上调整,此时该树已经是大堆了。

例如,现在我们在该大堆的末尾插入数据88。
image.png
我们先将88与其父结点54进行比较,发现88比其父结点大,则交换父子结点的数据,并继续进行向上调整。
image.png
此时将88与其父结点87进行比较,发现88还是比其父结点大,则继续交换父子结点的数据,并继续进行向上调整。
image.png
这时再将88与其父结点89进行比较,发现88比其父结点小,则停止向上调整,此时该树已经就是大堆了。

image.png

//堆的向上调整(大堆)
void AdjustUp(vector<int>& v, int child)
{
    int parent = (child - 1) / 2; //通过child计算parent的下标
    while (child > 0)//调整到根结点的位置截止
    {
        if (v[parent] < v[child])//孩子结点的值大于父结点的值
        {
            //将父结点与孩子结点交换
            swap(v[child], v[parent]);
            //继续向上进行调整
            child = parent;
            parent = (child - 1) / 2;
        }
        else//已成堆
        {
            break;
        }
    }
}

堆的向下调整算法

以大堆为例,使用堆的向下调整算法有一个前提,就是待向下调整的结点的左子树和右子树必须都为大堆。
image.png

调整的基本思想如下:
1、将目标结点与其较大的子结点进行比较。
2、若目标结点的值比其较大的子结点的值小,则交换目标结点与其较大的子结点的位置,并将原目标结点的较大子结点当作新的目标结点继续进行向下调整;若目标结点的值比其较大子结点的值大,则停止向下调整,此时该树已经是大堆了。

例如,将该二叉树从根结点开始进行向下调整。(此时根结点的左右子树已经是大堆)
image.png
将60与其较大的子结点88进行比较,发现60比其较大的子结点小,则交换这两个结点的数据,并继续进行向下调整。
image.png
此时再将60与其较大的子结点87进行比较,发现60比其较大的子结点小,则再交换这两个结点的数据,并继续进行向下调整。
image.png
这时再将60与其较大的子结点54进行比较,发现60比其较大的子结点大,则停止向下调整,此时该树已经就是大堆了。
image.png

//堆的向下调整(大堆)
void AdjustDown(vector<int>& v, int n, int parent)
{
    //child记录左右孩子中值较大的孩子的下标
    int child = 2 * parent + 1;//先默认其左孩子的值较大
    while (child < n)
    {
        if (child + 1 < n&&v[child] < v[child + 1])//右孩子存在并且右孩子比左孩子还大
        {
            child++;//较大的孩子改为右孩子
        }
        if (v[parent] < v[child])//左右孩子中较大孩子的值比父结点还大
        {
            //将父结点与较小的子结点交换
            swap(v[child], v[parent]);
            //继续向下进行调整
            parent = child;
            child = 2 * parent + 1;
        }
        else//已成堆
        {
            break;
        }
    }
}

priority_queue的模拟实现

成员函数实现方法
push在容器尾部插入元素后进行一次向上调整算法
pop将容器头部和尾部元素交换,再将尾部元素删除,最后从根结点开始进行一次向下调整算法
top返回容器的第0个元素
size返回容器的当前大小
empty判断容器是否为空
namespace cl //防止命名冲突
{
    //比较方式(使内部结构为大堆)
    template<class T>
    struct less
    {
        bool operator()(const T& x, const T& y)
        {
            return x < y;
        }
    };
    //比较方式(使内部结构为小堆)
    template<class T>
    struct greater
    {
        bool operator()(const T& x, const T& y)
        {
            return x > y;
        }
    };
    //优先级队列的模拟实现
    template<class T, class Container = vector<T>, class Compare = less<T>>
    class priority_queue
    {
    public:
        //堆的向上调整
        void AdjustUp(int child)
        {
            int parent = (child - 1) / 2; //通过child计算parent的下标
            while (child > 0)//调整到根结点的位置截止
            {
                if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
                {
                    //将父结点与孩子结点交换
                    swap(_con[child], _con[parent]);
                    //继续向上进行调整
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else//已成堆
                {
                    break;
                }
            }
        }
        //插入元素到队尾(并排序)
        void push(const T& x)
        {
            _con.push_back(x);
            AdjustUp(_con.size() - 1); //将最后一个元素进行一次向上调整
        }
        //堆的向下调整
        void AdjustDown(int n, int parent)
        {
            int child = 2 * parent + 1;
            while (child < n)
            {
                if (child + 1 < n&&_comp(_con[child], _con[child + 1]))
                {
                    child++;
                }
                if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
                {
                    //将父结点与孩子结点交换
                    swap(_con[child], _con[parent]);
                    //继续向下进行调整
                    parent = child;
                    child = 2 * parent + 1;
                }
                else//已成堆
                {
                    break;
                }
            }
        }
        //弹出队头元素(堆顶元素)
        void pop()
        {
            swap(_con[0], _con[_con.size() - 1]);
            _con.pop_back();
            AdjustDown(_con.size(), 0); //将第0个元素进行一次向下调整
        }
        //访问队头元素(堆顶元素)
        T& top()
        {
            return _con[0];
        }
        const T& top() const
        {
            return _con[0];
        }
        //获取队列中有效元素个数
        size_t size() const
        {
            return _con.size();
        }
        //判断队列是否为空
        bool empty() const
        {
            return _con.empty();
        }
    private:
        Container _con; //底层容器
        Compare _comp; //比较方式
    };
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

裙下的霸气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值