目录
1.容器适配器
1.1什么是适配器
1.2STL标准库中stack和queue的底层结构
1.3deque的简单介绍
1.3.1deque的原理介绍
1.3.2deque的优点和缺陷
1.3.3deque和vector进行排序的性能对比
1.4为什么选择deque作为stack和queue的底层默认容器
2.stack的介绍和模拟实现
2.1stack的介绍
2.2stack的模拟实现
2.2.1传统栈的结构
2.2.2利用vector作为stack的底层容器
2.2.3模板中加一个容器类型参数,实现不同容器作为stack的底层容器
3.queue的介绍和模拟实现
3.1queue的介绍
3.2queue的模拟实现
3.2.1传统队列的结构
3.2.2利用list作为queue的底层容器
3.2.3模板中加一个容器类型参数,实现不同容器作为queue的底层容器
1.容器适配器
1.1什么是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另一个接口。
如上图,需要将两个插口的插头转换为插座能使用的三个插口的插头,就需要一个适配器进行转换,适配器也可以叫做转换器。
1.2STL标准库中stack和queue的底层结构
虽然stack和queue中也可以存放元素,但是STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这里因为stack和queue只是对其他容器的接口进行了包装,所以stack和queue相当于上述三个抽口的插头,底层容器相当于两个插口的插头,用户需要一个stack或queue的结构,就调用其他的底层容器来弄出一个stack或queue,比如:
1.3deque的简单介绍
1.3.1deque的原理介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两段进行插入和删除操作(普通队列只能在一段插入另一端删除),且时间复杂度为O(1).与vector相比,头插效率高,不需要挪动元素;与list相比,空间利用率高。
deque并不是真正连续的空间,而是由一段段小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
双端队列底层是一段假想的连续空间,实际是分段连续的,为了维护其"整体连续"以及随机访问的假想,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
如上图,deque的存储空间放在一个中控器(可以理解为内存中开辟的一块用于存储deque里面数据的空间)里面,里面的每一方框就是一个缓冲器(这里的缓冲区相当于二维数组的每一行)。上述表明deque的迭代器中有4个东西:(1)node:表示指向的是哪一个缓冲区(相当于表面指向二维数组里面的哪一行)。(2)cur:表面在这个缓冲区的位置。(3)first和last:记录该缓冲区的起始位置和结束位置。具体怎么进行维护的这里就不过多的进行介绍了。
1.3.2deque的优点和缺陷
(1)优点:与vector相比,deque的优势是:头部的插入和删除时,不需要挪动元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此在头部进行插入和删除效率比vector高。与list相比,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
(2)缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某一小空间的边界,导致效率低下,而序列场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,而目前能看到的一个应用就是STL中作为stack和queue的底层数据结构。
1.3.3deque和vector进行排序的性能对比
这里在vs2022的Release下对vector和deque进行排序的性能比较,第一个是分别在各自容器中进行排序之后时间消耗的比较,第二个是将deque中的数据copy到vector进行排序后返回到deque中与deque中自己排序的时间消耗进行比较。
#include <iostream>
#include <vector>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>
using namespace std;
void test_op1()
{
srand(time(0));
const int N = 1000000;
deque<int> dq;
vector<int> v;
for (int i = 0; i < N; i++)
{
auto e = rand() + i;
v.push_back(e);
dq.push_back(e);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
sort(dq.begin(), dq.end());
int end2 = clock();
cout << "vector: " << end1 - begin1 << endl;
cout << "deque: " << end2 - begin2 << endl;
}
void test_op2()
{
srand(time(0));
const int N = 1000000;
deque<int> dq1;
deque<int> dq2;
for (int i = 0; i < N; ++i)
{
auto e = rand() + i;
dq1.push_back(e);
dq2.push_back(e);
}
int begin1 = clock();
sort(dq1.begin(), dq1.end());
int end1 = clock();
int begin2 = clock();
// 拷贝到vector
vector<int> v(dq2.begin(), dq2.end());
sort(v.begin(), v.end());
dq2.assign(v.begin(), v.end());
int end2 = clock();
printf("deque sort:%d\n", end1 - begin1);
printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}
int main()
{
test_op1();
test_op2();
return 0;
}
1.4为什么选择deque作为stack和queue的底层默认容器
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
(1)stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或两段进行操作。
(2)在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
2.stack的介绍和模拟实现
2.1stack的介绍
2.2stack的模拟实现
2.2.1传统栈的结构
template <class T>
class Stack
{
private:
T* _a; //用数组存储元素
size_t _top;
size_t _capacity;
};
2.2.2利用vector作为stack的底层容器
这里在stack类里面有一个vector的对象,所有的stack接口通过调用vector的接口进行实现。
namespace XiaoC
{
template<class T>
class stack
{
public:
//会调用vector的构造函数进行构造
stack()
{}
void push(const T& x)
{
_c.push_back(x); //调用vector的push_back接口
}
void pop()
{
_c.pop_back();
}
//取栈顶元素
T& top() const
{
return _c.back();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
std::vector<T> _c;
};
}
2.2.3模板中加一个容器类型参数,实现不同容器作为stack的底层容器
这里默认给的容器是deque,对于stack来说只要支持push_back()和pop_back()线性结构容器就能用来当作底层容器。
#pragma once
#include <deque>
namespace XiaoC
{
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;
};
}
3.queue的介绍和模拟实现
3.1queue的介绍
3.2queue的模拟实现
3.2.1传统队列的结构
// 链式结构:表示队列中每个节点的结构
typedef struct QListNode
{
struct QListNode* _pNext;
QDataType _data;
}QNode;
// 队列的结构,用两个指针来维护一个队列
typedef struct Queue
{
QNode* _front;
QNode* _rear;
}Queue;
3.2.2利用list作为queue的底层容器
这里queue里面有一个list对象,所有queue的接口通过调用list的接口进行实现。
#pragma once
#include <deque>
namespace XiaoC
{
template<class T>
class queue
{
public:
queue()
{}
void empty()
{
return _c.empty();
}
void size()
{
return _c.size();
}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& front()
{
return _c.front();
}
const T& front()
{
return _c.front();
}
T& back()
{
return _c.back();
}
const T& back() const
{
return _c.back();
}
private:
std::list<T> _c;
};
}
3.2.3模板中加一个容器类型参数,实现不同容器作为queue的底层容器
这里默认给的容器是deque,对于queue来说只要支持push_back()和pop_front()线性结构容器就能用来当作底层容器。
namespace XiaoC
{
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() const
{
return _con.front();
}
const T& back() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}