⭐️今天我先为大家介绍STL中的stack和queue容器适配器,它的底层是用其其它容器来实现的,其后我会介绍另一个容器适配器——priority_queue(优先级队列)。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code
stack是一种先进后出的容器,之前的数据结构中有介绍过。
总结几点:
实例演示:
void TestStack()
{
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
// 没有迭代器
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
queue是队列,是一种先进先出的容器。
总结几点:
实例演示:
void TestQueue()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
适配器: 一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。
可以看出的是,这两个容器相比我们之间见过的容器多了一个模板参数,也就是容器类的模板参数,他们在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,它们的底层是其他容器,堆其他容器的接口进行了包装,它们的默认是使用deque(双端队列)(后面会介绍)
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
deque底层结构
它并不是一段连续的空间,而是由多个连续的小空间拼接而成,相当于一个动态的二维数组。
如下图:
deque的缺点:
下面是通过排序来测试vector和deque随机访问的效率
void TestDeque()
{
srand((unsigned int)time(nullptr));
deque<int> d;
vector<int> v;
for (size_t i = 0; i < 100000; ++i)
{
int randNum = rand();
v.push_back(randNum);
d.push_back(randNum);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
sort(d.begin(), d.end());
int end2 = clock();
cout << "vector排序用时:" << end1 - begin1 << "ms" << endl;
cout << "deque排序用时:" << end2 - begin2 << "ms" << endl;
}
代码运行结果如下:
容易看出,deque的随机访问的效率是比vector低很多的。
deque可以作为stack和queue底层默认容器的原因:
stl中的stack和vector是通过容器适配转换过来的,不是原生实现的,为了复用
template<class T, class Container = deque<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()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
template<class T, class Container = deque<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()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
实例演示:
void test_priority_queue()
{
priority_queue<int, vector<int>> pq;
pq.push(5);
pq.push(7);
pq.push(4);
pq.push(2);
pq.push(6);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
其中模板中有三个参数,最后一个参数是仿函数(下面后介绍),也就是指明优先级队列是按照升序还是降序来存数据的。
template<class T, class Container = vector<T>, class Compare = less<T>>// 默认是小于
class priority_queue
{
public:
private:
Container _con;
Compare _com;
};
仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
实例如下:
// 仿函数 就是一个类重载了一个(),operator(),可以像函数一样使用
template<class T>
struct greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
template<class T>
struct less
{
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
可以看出,仿函数就是用一个类封装一个成员函数operator(),使得这个类的对象可以像函数一样去调用。
实例演示
template<class T>
struct IsEqual
{
bool operator()(const T& a, const T& b)
{
return a == b;
}
};
void test()
{
IsEqual<int> ie;
cout << ie(2, 3) << endl;// 该类实例化出的对象可以具有函数行为
}
// 仿函数 就是一个类重载了一个(),operator(),可以想函数一样使用
template<class T>
struct greater
{
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
template<class T>
struct less
{
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
数据结构的博客有介绍过,这里直接上代码(都是在大堆或小堆的前提下操作):
向上调整: 从最后一个数往上调整
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent])//< 建小堆 > 建大堆
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下调整: 从第一个往下调整
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < (int)size())
{
if (child + 1 < (int)size() && _con[child + 1] > _con[child])
{
++child;
}
if (_con[child] > _con[parent])// 建小堆
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
这两个函数用仿函数实现后如下:
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_com(_con[parent], _con[child]))// _con[child] > _con[parent]
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < (int)size())
{
if (child + 1 < (int)size() && _com(_con[child], _con[child + 1]))// _con[child + 1] > _con[child]
{
++child;
}
if (_com(_con[parent], _con[child]))// _con[child] > _con[parent]
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp((int)size() - 1);
}
void pop()
{
assert(!empty());
swap(_con[0], _con[(int)size() - 1]);
_con.pop_back();
AdjustDown(0);
}
T& top()
{
assert(!empty());
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return size() == 0;
}