文章目录
- [1. 前言](#1. 前言)
- [2. stack的介绍](#2. stack的介绍)
- [3. stack的实现](#3. stack的实现)
-
- [1. 铺垫](#1. 铺垫)
- [2. push](#2. push)
- [3. pop](#3. pop)
- [4. top](#4. top)
- [5. size](#5. size)
- [6. empty](#6. empty)
- [7. 测试](#7. 测试)
- [4. queue的介绍](#4. queue的介绍)
- [5. queue的实现](#5. queue的实现)
-
- [1. 铺垫](#1. 铺垫)
- [2. push](#2. push)
- [3. pop](#3. pop)
- [4. front](#4. front)
- [5. back](#5. back)
- [6. size](#6. size)
- [7. empty](#7. empty)
- [8. 测试](#8. 测试)
- [6. deque的简单介绍](#6. deque的简单介绍)
-
- [1. 为什么不能取代vector](#1. 为什么不能取代vector)
- [2. 为什么不能取代list](#2. 为什么不能取代list)
- [3. 对于stack和queue而言](#3. 对于stack和queue而言)
- [7. stack和queue的源代码](#7. stack和queue的源代码)
-
- [1. stack.h](#1. stack.h)
- [2. queue.h](#2. queue.h)
- [3. test.cpp](#3. test.cpp)
1. 前言
在前面我们已经学习了string、vector、list等stl容器的使用以及实现,今天我们来看一下stack和queue,其实就是对应我们的栈和队列------>>>点击查看栈和队列
2. stack的介绍

- stack是一种容器配置器,什么是容器配置器呢?我们在后续实现的时候就能深切感受到了,我们这里粗略形容一下就是stack是可以用其他的容器来实现的,比如vector、list,这里底层实现默认是采用deque,我们后续会简单介绍deque
- stack就是我们数据结构中的栈,先进后出,只能从容器的一端进行数据的插入和删除操作
- 适配stack的容器支持以下接口:(push_back,尾部插入数据),(pop_back,尾部删除数据),(back,获取尾部数据的引用),(size,获取元素的个数),(empty,判空)
3. stack的实现
1. 铺垫
- 我们依然是采用模板来设计的,所以我们创建两个文件分别为stack.h和test.cpp分别用于实现基本功能和测试
- 我们前面说到,stack是一种容器适配器,所以它的模板参数是传一个容器,默认是deque,为了方便大家理解,我们这里先默认使用vector。
cpp
#pragma once
#include<iostream>
#include<vector>
#include<list>
using namespace std;
template<class T, class Container = vector<T>>
class stack
{
public:
private:
Container _con;
};
2. push
cpp
void push(const T& x)
{
_con.push_back(x);
}
- 我们的push是为了尾插一个数据,实现是不是超级简单,这里我们直接使用我们传入的容器类型的push_back就可以实现我们的push
- 针对这里的T类型,我们不进行修改所以使用const修饰,由于T可以是任意容器类型,所以我们这里直接传T的引用可以减少传参的消耗
- 如果我们传入的容器不支持push_back呢?相信大家会有这样的烦恼,这是因为大家接触容器配置器比较少,比较陌生,这样给大家举个例子,我们使用list会用下标访问吗?肯定不会啊,为什么?因为会报错会不支持啊,所以如果我们这里传入的容器不支持push_back的话就会直接报错,也不需要加多余的判断,我们传入的容器自己就报错了
3. pop
cpp
void pop()
{
_con.pop_back();
}
- 我们的pop是为了尾删一个数据,实现起来也是十分简单,我们直接使用传入的容器自己的pop_back就可以了
- 这里针对如果传入的容器不支持pop_back的问题我们就不再赘述了,原因跟在push那里是一样的
4. top
cpp
T& top()
{
return _con.back();
}
- top是为了返回栈顶数据的引用,我们直接调用对应容器的back接口就可以返回末尾数据的引用了
- 为了返回栈顶数据的引用所以我们这里返回值类型是T&
5. size
cpp
size_t size() const
{
return _con.size();
}
- size是为了获取元素的个数,所以我们直接调用传入容器的size接口就可以了
- 由于容器的大小不可能为负数,所以我们这里使用的是size_t类型
- 我们是这里针对size是不可能允许做任何修改的,所以我们这里直接使用const修饰this指针就可以了
6. empty
cpp
bool empty() const
{
return _con.empty();
}
- empty就是来判空的,我们只需要调用对应容器的empty接口就可以了
- 这里我们也不做任何修改所以直接用const修饰this指针就可以
7. 测试
cpp
#include "stack.h"
#include<stack>
int main()
{
William::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
cout << st.size() << endl;
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
return 0;
}

4. queue的介绍

- queue也是一种容器配置器,默认也是采用deque来实现
- queue就是我们数据结构种的队列,先进先出,元素从队尾入队列,从队头出队列
- 适配queue的容器应该支持以下接口:(push_back,尾插),(pop_front,头删),(front,获取头部数据的引用),(back,获取尾部数据的引用),(size,获取数据个数),(empty,判空)
5. queue的实现
1. 铺垫
- 我们依然是采用模板来设计的,所以我们创建两个文件分别为queue.h和test.cpp分别用于实现基本功能和测试
- 我们前面说到,queue也是一种容器适配器,所以它的模板参数是传一个容器,默认是deque,为了方便大家理解,我们这里先默认使用list。
cpp
#pragma once
#include<iostream>
#include<vector>
#include<list>
using namespace std;
namespace William
{
template<class T, class Container = list<T>>
class queue
{
public:
private:
Container _con;
};
}
2. push
cpp
void push(const T& x)
{
_con.push_back(x);
}
- 我们的push是为了尾插一个数据,这里我们直接使用我们传入的容器类型的push_back就可以实现我们的push
- 针对这里的T类型,我们不进行修改所以使用const修饰,由于T可以是任意容器类型,所以我们这里直接传T的引用可以减少传参的消耗
3. pop
cpp
void pop()
{
_con.pop_front();
}
- 我们的pop是为了尾删一个数据,我们直接使用传入的容器自己的pop_back就可以了
4. front
cpp
T& front()
{
return _con.front();
}
- front是用于获取头部的数据的引用,这里我们调用front即可,其返回的就为头部数据的引用
5. back
cpp
T& back()
{
return _con.back();
}
- back是用于获取尾部的数据的引用,这里我们调用back即可,其返回的就为尾部数据的引用
6. size
cpp
size_t size() const
{
return _con.size();
}
- size是为了获取元素的个数,所以我们直接调用传入容器的size接口就可以了
- 由于容器的大小不可能为负数,所以我们这里使用的是size_t类型
- 我们是这里针对size是不可能允许做任何修改的,所以我们这里直接使用const修饰this指针就可以了
7. empty
cpp
bool empty() const
{
return _con.empty();
}
- empty就是来判空的,我们只需要调用对应容器的empty接口就可以了
- 这里我们也不做任何修改所以直接用const修饰this指针就可以
8. 测试
cpp
#include "queue.h"
int main()
{
William::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << q.front() << " " << q.back() << endl;
cout << q.size() << endl;
while(!q.empty())
{
cout << q.front() << " ";
q.pop();
}
return 0;
}

6. deque的简单介绍


- deque双端队列,是一种双开口的"连续"空间的数据结构,双开口的含义是可以在头尾两端进行数据的插入和删除操作,且时间复杂度为O(1)
- deque也可以这样理解,就是list和vector的缝合怪,既可以很好地实现头插头删,也支持下标访问
- 它的底层主要是四个指针,分别是cur,first,last,node,它的结构是有一个中控数组,这个中控数组是从中间的位置开始插入一段段buff(从中间开始方便头插),cur指向当前所处的buff,first指向当前buff的起始位置,last指向当前buff的结束位置,node指向当前buff在中控数组所处的位置,我们中控数组是连续的,所以node+1就是下一个buff的地址
1. 为什么不能取代vector
- 首先我们要肯定的是它肯定是比vector的头插头删效率高的
- 但是它的下标访问就跟vector差的有点多了,在初步了解deque之后我们可以感受到deque的下标访问的计算量还是不小的,deque也是可以使用sort函数的,大家可以去自己测试一下vector的sort和deque的sort差了多少,甚至先把deque拷贝到vector排序后再拷贝回deque的时间都比直接排序deque要快
2. 为什么不能取代list
- deque支持下标访问这一点是比list厉害不少的,而且它也不需要频繁地在堆上申请空间,缓存利用率也比list高,头插头删效率也很好
- 但是在中间插入数据就垮掉了,deque在中间插入数据也是需要挪动数据的
3. 对于stack和queue而言
- 对于stack和queue来说用deque确实很合适
- stack和queue是不需要对中间的数据做任何改动的,它俩只涉及头尾数据的改动,这就完美符合stack和queue的要求了
7. stack和queue的源代码
1. stack.h
cpp
#pragma once
#include<iostream>
#include<vector>
#include<list>
using namespace std;
namespace William
{
template<class T, class Container = vector<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
2. queue.h
cpp
#pragma once
#include<iostream>
#include<vector>
#include<list>
using namespace std;
namespace William
{
template<class T, class Container = list<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
3. test.cpp
cpp
#include "stack.h"
#include "queue.h"
int main()
{
William::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
cout << st.size() << endl;
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
William::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << q.front() << " " << q.back() << endl;
cout << q.size() << endl;
while(!q.empty())
{
cout << q.front() << " ";
q.pop();
}
return 0;
}