1. stack的介绍、使用和实现
1.1 stack的介绍
stl里的stack其实和数据结构内的stack和前面数据结构的栈不能说百分百一样,但也有百分之90是一样的,他们的特性都是LIFO(last in first out)先进后出的原则,前面有类似的这里就不过多讲述;
这个是C++ stack的文档:stack - C++ Reference (cplusplus.com)
1.2 stack的使用
stack的主要的使用接口就那么几个,我们这里就直接上个主要的接口图和使用的代码;如下图和代码所示:
#include<iostream>
#include<stack>
using namespace std;void TestStack()
{//同前面list一样,他们都是个模板stack<int> st;//empty的使用if (!st.empty()){cout << " stack is not empty" << endl;}else{cout << "stack is empty" << endl;}//push的使用st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);//empty的使用if (!st.empty()){cout << " stack is not empty" << endl;}else{cout << "stack is empty" << endl;}//size的使用cout << "The size of stack is:> " << st.size() << endl;//top的使用cout << "The top element of stack is:> " << st.top() << endl;//因为栈和队列没有迭代器,如果有迭代器的话那就可以访问任意位置的数据了,//不符合后进先出的原则//所以如果我们想打印栈内的数据需要这样走;cout << "The element of stack are:> ";while (!st.empty()){cout << st.top() << " ";st.pop();}cout << endl;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);//pop的使用st.pop();st.pop();cout << "After pop the element of stack are:> ";while (!st.empty()){cout << st.top() << " ";st.pop();}
}int main()
{TestStack();return 0;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
1.3 stack的实现
由于在stl库内 stack不是一个简单的容器,而是一个容器适配器Container adaptor,举个例子,容器适配器就类似家里的插头,我们不仅可以用手机充电头去插头那给手机充电,还可以用电脑电源的充电头去给电脑充电,而手机充电头就类似list<int>,电脑充电头就类似vector<int>;如下代码可见;
#include<iostream>
#include<deque>
#include<stack>
#include<vector>
#include<list>
using namespace std;
//#include"stack.h"void teststack()
{//适配list容器stack<int, list<int>> st1;st1.push(1);st1.push(2);st1.push(3);st1.push(4);st1.push(5);cout << "The top is:> " << st1.top() << endl;st1.pop();st1.pop();cout << "The top is:> " << st1.top() << endl;while (!st1.empty()){cout << st1.top() << " ";st1.pop();}cout << endl;//适配vector容器stack<int, vector<int>> st2;st2.push(1);st2.push(2);st2.push(3);st2.push(4);st2.push(5);cout << "The top is:> " << st2.top() << endl;st2.pop();st2.pop();cout << "The top is:> " << st2.top() << endl;while (!st2.empty()){cout << st2.top() << " ";st2.pop();}
}
int main()
{teststack();return 0;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
那么我们实现stack的时候我们的类模板就不止一个传入的类型,还有一个类类型的变量接收;那具体的实现步骤就直接看代码,因为这个实现的底层其实也就是数组,但也可以用链表去实现前面数据结构的栈的篇章说过;如下代码所示:
#pragma once
#include"priority_queue.h"
#include<deque>
namespace lwt
{//stack在stl库内是被称作container adaptor(容器适配器)template<class T, class Container = deque<T>>class stack{public://尾插void push(const T& x){_con.push_back(x);}//尾删void pop(){_con.pop_back();}//取栈顶元素const T& top()const{return _con.back();}size_t size()const{return _con.size();}bool empty()const{return _con.empty();}private:Container _con;};
}
这里的成员变量用Container就是为了达到容器适配器的效果,如果传入的是vector<int>,那么就会执行vector<int> _con; 然后再进行插入删除,list也一样;那上面有一点就是deque是什么玩意,先保留这个疑问,下面一会会讲到;
2. queue的介绍、使用和实现
2.1 queue的介绍
stl里的queue和数据结构里的queue队列也类似,也是先进先出FIFO的特性,所以也不过多讲述;
这个是C++的queue的文档:queue - C++ Reference (cplusplus.com)
2.2 queue的使用
如下图和代码所示:
#include<iostream>
#include<deque>
#include<queue>
#include<stack>
#include<vector>
#include<list>
using namespace std;int main()
{queue<int, list<int>> que1;que1.push(1);que1.push(2);que1.push(3);que1.push(3);que1.push(4);que1.push(5);while (!que1.empty()){cout << que1.front() << " ";que1.pop();}cout << endl;//size的使用que1.push(1);que1.push(2);que1.push(3);que1.push(3);que1.push(4);que1.push(5);cout << "The size of que1 is:> " << que1.size() << endl;que1.front() = 9;cout << "The front of que1 is:> " << que1.front() << endl;return 0;
}
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
2.3 queue的实现
stl库内的queue和上面的stack一样,都是容器适配器;增删查改实现的话其实就和前面的数据结构的队列实现的思路一样,所以不在这里过多讲述,直接上图和代码;
#pragma once
namespace lwt
{#include<deque>template<class T, class Container = deque<T> >class queue{public://入数据void push(const T& x){_con.push_back(x);}//出队列void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};
}
3. deque的介绍
在上面我们看到stack和queue的模板的类类型的缺省参数是deque,但上面没有讲,因为我想在下面另外讲解;deque,可能我们看到有个que就认为他就是队列,所以底层可能是用链表实现的,其实并不然,deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,也可以说deque是list和vector的杂交版;
但实质上,其实deque并不是真正的连续的空间,而是由一段段连续的小空间拼排而成的,实际上deque就类似一个动态的二维数组;其结构如下图所示:
红色区域是一个中控数组,每次的插入和删除都是在中控数组的中间开空间,这个效率就很高了,为什么高呢?相比于链表,他的尾插和头插的效率更高,因为他每次都是开绿色块那样的空间大小,而链表每次头尾插需要进行开辟新的空间,开辟新的空间会造成效率降低;对于vector来说,头插就很方便了,他只需要在前面再new多一块空间,然后从后往前插入完成头插,而不需要挪动数据,
下图是他的具体结构
3.1 deque的缺陷
当deque遍历的时候要频繁的检测first是否等于last,如果等于就需要node+1,导致效率低下,所以一般要使用线性结构的时候大多数优先考虑list和vector,deque的应用场景不多,目前看见的应用是在STL里用其作为stack和queue的底层结构;
3.2 为什么选择deque作为stack和queue的底层结构
4. priority_queue的介绍和实现
4.1 priority_queue的介绍
priority_queue又称优先级队列,它的第一个元素总是它所包含的元素中最大的,那这是不是就有点像我们前面数据结构学过的堆;其实优先级队列的底层就是堆,首先我们先来回忆一下堆的性质,堆分为大堆和小堆,大堆的每个树的父节点是树中最大的,小堆则反之,但是STL的priority_queue和我们数据结构那实现的堆的不同是,他是一个容器适配器,即只要符合他的特性的容器都可以拿来当他的底层,如下图就是优先级队列的特性;
而标准类vector和deque满足这些条件;还需要支持随机访问迭代器,因为完成建堆等操作,而 堆的底层就是顺序表,那就需要支持随机访问迭代器;
4.2 仿函数基础
在实现优先级队列之前我们还需要提一个点就是仿函数,仿函数顾名思义就是类似函数的东西,他实质上是一个类,他是重载了“()”的一个类,作用是用来做比较,例如如果我们想实现一个大堆的话,我们就需要修改建堆的大于小于号,那是不是太麻烦了,所以我们就延伸出了仿函数这个东西;举个例子,比如说我想让条件为arr[parent]>arr[child]的时候我们就可以定义一个类作为仿函数greater(arr[parent], arr[child] ),而我我想让条件改为arr[parent]<arr[child]的话就定义一个类作为仿函数less(arr[parent], arr[child];如下代码可见,这是使用,就需要在创建priority_queue的时候在最后面加一个greater<int>;
#include <vector>
#include <queue>
#include<iostream>
#include <functional> // greater算法的头文件
using namespace std;void TestPriorityQueue()
{// 默认情况下,创建的是大堆,其底层按照小于号比较vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };priority_queue<int> q1;for (auto& e : v)q1.push(e);cout << q1.top() << endl;// 如果要创建小堆,将第三个模板参数换成greater比较方式priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());cout << q2.top() << endl;
}int main()
{TestPriorityQueue();return 0;
}
4.2 priority_queue的实现
优先级队列的实现也很简单,其实就是建堆,只不过在这里面添加多了一个仿函数进行判断是建小堆还是大堆,如下代码所示:
#pragma once
#include<vector>
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;}
};namespace lwt
{template<class T, class Container = vector<T> , class Compare = Less<T>>class priority_queue{public:void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_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 parent){size_t child = parent * 2 + 1;Compare com;while (child < _con.size()){//看左右孩子谁大谁小,大堆那我们就要找到小的if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){//pop就是堆删swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}const T& top(){return _con[0];}size_t size() const{return _con.size();}bool empty()const{return _con.empty();}private:Container _con;};}
END!