目录
前言
本文主要讲解在C++中栈与队列的使用以及模拟实现, 关于栈和队列相关概念,我们早就在数据结构中的栈与队列介绍过,本文就不再复述,如果还不了解可以点击下方链接,先去看看关于栈与队列的基本概念;
一、C++中的栈
1、适配器容器
在C++中,栈实际上时一种适配器容器,什么叫适配器容器呢?适配器容器实际上是对一般序列容器的封装,使其具有某种特殊功能,实际上,我们接下来会学习的queue与priority_queue都是适配器容器;
这里有一个关于模板知识的补充,实际上,模板的类型还可以是容器,我们可以给模板参数缺省值,并且,给缺省值的规则与我们给函数缺省值的规则一致; 函数缺省复习入口
这里我们发现给的缺省值是deque,deque也是一种容器,也叫双端队列,关于双端队列,后面会详细介绍;
2、栈的使用
关于栈的使用,一共有如下接口,均查阅于文档;文档链接
如果学习过我们之前C语言版的栈,相信大部分接口大家看一眼大概都会使用;我们用C语言模拟实现的栈的接口命名也来自STL中的stack;下面我们粗略的通过代码来学习这些接口的使用;(emplace暂可不用了解)
void test_stack()
{
// 第二个模板参数默认会使用双端队列
stack<int> st1;
// 指定使用vector适配容器
stack<int, vector<int>> st2;
// 插入数据
st1.push(1);
st1.push(2);
st1.push(3);
st1.push(4);
st1.push(5);
// 判空
while (!st1.empty())
{
// 取栈顶数据
cout << st1.top() << " ";
// 出栈
st1.pop();
}
cout << endl;
}
3、栈的模拟实现
栈的模拟实现也很简单,有了之前学习C语言版本栈的模拟实现的基础,这里的实现就更简单了;
#include <deque>
namespace MySpace
{
template<class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_back();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
private:
Container _con;
};
}
主要的思想还是复用模板容器的接口,这里仅仅只需大概40就可以模拟实现出一个stack;真的真的不要太简单;这里可以充分体会到了C++模板的魅力!(我为模板举大旗)
二、C++的队列
1、队列的使用
同样,队列也是适配器容器;与stack一样;连模板参数也是一模一样,这里就不做过多的介绍;
queue主要有以下接口;大部分接口大家也在之前介绍栈与队列那篇文章有做介绍;
void test_queue()
{
// 第二个容器模板参数使用默认的deque
queue<int> q1;
// 第二个模板参数使用list
queue<int, list<int>> q2;
// 队尾插入数据
q1.push(1);
q1.push(2);
q1.push(3);
q1.push(4);
q1.push(5);
// 判空
while (!q1.empty())
{
cout << "队头: " << q1.front() << "队尾: " << q1.back() << endl;
// 队头出数据
q1.pop();
}
cout << endl;
}
2、队列的模拟实现
队列的模拟实现同样也很简单;代码如下;
#include <deque>
namespace MySpace
{
template<class T, class Container = std::deque<T>>
class queue
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_front();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
private:
Container _con;
};
}
关于栈与队列这一部分的实现对于我们来说几乎没有什么难度,对于栈与队列,实际上主要难点在于这两个容器在OJ题中的使用;我们可以通过以下这几个OJ题来加强我们对这两个容器的理解;
三、deque
deque是双端队列,我们可以在队头与队尾入数据与出数据,实际上,它是vector与list的结合;
如上图结构,deque实际上有一个中控数组,其中储存的都是数组指针,指向一个数组,每个数组都可以储存一定的数据(SGI实现版本);我们当我们需要头插时,我们直接在中控中,创建一个数组指针,接着往其中buf缓冲区写入数据,尾插也类似;由于每个缓冲区大小差不多,因此我们可通过计算实现类似数组的随机访问;
总结:
实际上,deque的使用并不多,既然deque又有vector的优点,也有list的优点,那为什么用的不多呢?实际上,deque有以下优缺点;
优点:
- 可随机访问
- 可头插尾插,且效率不低
- 扩容代价低
缺点:
- 中间的插入删除效率低
- 不适合遍历,因为遍历时,频繁检测是否要进入下一个buf区间
最后,之所以使用deque作为stack与queue的默认适配容器,是因为我们在使用stack与queue的时候一般没有遍历的需求,也没有中间位置插入的需求,deque还可头尾插删,效率也不低,因此选择deque作为默认的适配容器;