目录
stack和queue为什么选择deque作为底层默认容器:
容器适配器
什么是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
STL标准库中stack和queue的底层结构
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,priority_queue默认使用vector作为其底层容器。
(简单来说就是,stack和queue没必要自己再实现一遍那些逻辑操作,本身需要的操作并不多,并且也没有对底层存储结构有要求,而又有了vector list deque等成熟的,功能完善的容器,所以只需要对那些容器进行简单封装即可,适配器即将其他容器适配为我们现在需要的容器,对函数等进行封装转换。)
stack
1. 后进先出 LIFO
2. 是一种容器适配器,只能从容器的一端进行元素的插入,提取,删除操作。
3. std::stack默认使用deque作为其适配容器,适配容器需要支持empty size push_back pop_back操作,vector list都符合
#ifndef STL_STACK_H
#define STL_STACK_H
#include <deque>
#include <cstdlib>
namespace yzl
{
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();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
// vector<T> _con;
Container _con;
};
}
queue
1. 先进先出,FIFO
2. 队列是一种容器适配器,支持front back双端获取元素,元素从队尾入队列,从队头出队列。
3. std::queue默认使用deque为容器适配器,底层容器必须支持push_back pop_front back front操作,deque,list都可以,但vector不适合做queue的适配容器。
#ifndef STL_QUEUE_H
#define STL_QUEUE_H
#include <deque>
#include <cstdlib>
namespace yzl
{
// 容器不适于stack,因为没有pop_front
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();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
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;
};
}
#endif //STL_QUEUE_H
priority_queue
1. 优先级队列是一种容器适配器,它的第一个元素总是它所包含的元素中最大的(最小的)。
2. 优先级队列的数据结构就是一个堆,且默认为大堆,第三个模板参数 class Compare = std::less<T>
3. 仅支持push pop top操作,也就是priority_queue只能获取当前堆结构的堆顶元素。
4. 大堆和小堆由第三个比较参数决定,less为大堆,greater是小堆
5. 底层容器应当支持随机访问迭代器(下标随机访问),push_back pop_back front操作,致使vector和deque适合做priority_queue的适配容器,list不适合,因为不支持随机访问迭代器(随机访问)
6. std::priority_queue默认使用vector是因为vector的随机访问比deque更高效。需要支持随机访问迭代器,以便始终在内部保持堆结构。在vector上又使用了堆算法将vector中元素构造成 堆的结构
namespace yzl
{
// less大堆,greater小堆
template<class T, class Container = std::vector<T>, class Compare = std::less<T> >
class priority_queue
{
private:
Container _con;
public:
priority_queue() = default;
template<class InputIterator>
priority_queue(InputIterator begin, InputIterator end)
{
while(begin != end)
{
_con.push_back(*begin);
++begin;
}
// 建堆,向下调整效率高
for(int i = (_con.size()-1-1)/2; i >= 0; --i)
{
adjust_down(i);
}
}
void push(const T& val)
{
_con.push_back(val);
adjust_up(_con.size()-1);
}
void adjust_up(size_t child)
{
Compare cmp;
size_t parent = (child-1)/2;
while(child > 0) // 向上调整终止条件
{
if(cmp(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child-1)/2;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_con[0], _con[_con.size()-1]);
_con.pop_back();
adjust_down(0);
}
void adjust_down(size_t parent)
{
Compare cmp;
size_t child = parent*2+1;
while(child < _con.size())
{
if(child + 1 < _con.size() && cmp(_con[child], _con[child+1]))
{
child+=1;
}
if(cmp(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
// 优先级队列的top返回值为const不可以修改。
const T& top() const
{
return _con.front();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
};
}
其实就是对一个容器进行堆算法,将其实现为一个堆。重点是向上调整和向下调整算法。还有建堆时,最高效的是从第一个非叶节点开始向下调整。
stack和queue为什么选择deque作为底层默认容器:
stack后进先出,只要push_back pop_back都可以,vector list deque都可以
queue先进先出,只要push_back pop_front都可以,list deque都可以
但是STL中对stack和 queue默认选择deque作为其底层容器,主要是因为:(这里的原因根本上还是vector list 和 deque的优劣比较)
1. 栈和队列不需要遍历(因此stack和queue没有迭代器),而vector是比deque遍历更快的
2. 在stack和queue元素增长时,deque比vector的效率高,因为扩容时不需要移动大量元素(基于deque的底层存储结构),并且deque不仅效率高,内存使用率也高。
deque的劣势是在中间插入删除,这是list的优势。还有元素随机访问的效率并不是太高,这也是vector的优势。而stack和queue不需要这方面。