欢迎来到博主的专栏——c++编程
博主ID:代码小豪
适配器adaptor
适配器看起来像一个容器,实际上adaptor并不属于容器的范畴,更像是一种专门用于某种容器的接口。
常用的适配器有stack、queue、priority_queue。
template <class T, class Container = deque<T> > class stack;
template <class T, class Container = deque<T> > class queue;
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
这些适配器的模板不仅仅能接收类型作为参数,也能接收容器的类型作为参数来实例化适配器。stack、queue、priority_queue允许用顺序容器来作为适配器的底层。顺序容器有vector、list、deque。因此这些适配器也被称为顺序容器适配器。
适配器的机制允许适配器选择一个容器作为底层,但是会提供特殊的接口来掩盖这些底层。比如以deque为底层的stack不允许头插元素,也不允许遍历整个容器。因此适配器更像是限定底层容器行为的容器
在下面,博主会模拟实现这三个适配器。但是相关的算法博主并不打算解释,因为这些内容博主已经在另一个专栏——c语言数据结构中讲过了。在文章的末尾,博主会附上链接。
stack
stack是一个先进后出(FILO)的数据结构,它只有一个头端的出口与入口,stack中插入元素、删除元素、访问元素都只能在头端进行操作。
由于stack的所有插入与删除的操作都在一端。因此,作为底层的容器必须支持在同一端进行数据的插入与删除操作,比如push_back,pop_back,push_front,pop_front等,因此可以作为stack适配器的底层容器有vectoor,deque,list。
而在这些容器当中,vector会有扩容造成额外的时间开销,而list的空间又较为臃肿,因此deque成为了stack最合适的底层容器,这是由于它拥有不错的头尾插入效率。
stack适配器的模拟实现如下:
template<class T,class container=deque<T>>
class stack
{
public:
stack(const container& copy_con=container())
:_con(copy_con) {;}
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(); }
bool empty()const { return _con.empty(); }
size_t size()const { return _con.size(); }
private:
container _con;//底层容器
};
其实从这段代码就很好的体现了,适配器更像是限定底层容器行为的容器这一特性。因此从代码中可以发现,stack并没有设计属于自己的操作,也没有除底层容器以外的数据成员,stack适配器中一切对于stack操作的函数,其实是在对底层容器进行操作。比如像stack上push一个元素,实际上是在容器的尾端插入元素,取stack栈顶元素,实际上是在取容器的尾端元素。
从这里就可以看出,适配器其实是一个封装了底层容器的接口。拿一个例子举例,底层容器就像一个电灯泡,而适配器就是这个电灯泡的灯罩,如果我们用不同的灯罩来封装灯泡,其发光的效果会发生变化,但是底层的电灯泡是不会变的,只是灯罩变了,于是效果也就跟着变了。
从下面这段测试案例,也能佐证这个性质:
我们可以用性质不同的容器来作为stack的底层容器,但是由于stack限制了这些容器的操作,因此它们看起来没有两样
void testmystack()
{
stack<int, vector<int>> stack1;//vector为底层
stack1.push(1);
stack1.push(2);
stack1.push(3);
stack1.push(4);
stack1.push(5);
while (!stack1.empty())
{
cout << stack1.top();
stack1.pop();
}
cout << endl;
stack<int, list<int>> stack2;//list为底层
stack2.push(1);
stack2.push(2);
stack2.push(3);
stack2.push(4);
stack2.push(5);
while (!stack2.empty())
{
cout << stack2.top();
stack2.pop();
}
cout << endl;
}
后续不再放出测试案例
queue
queue是一种先进先出(FIFO)的数据结构,它有两个端,头端作为出口,尾端作为入口,queue只允许在尾端插入元素,在头端删除元素,以及只允许在头端访问元素
queue只允许在尾端插入数据,在头端删除数据,因此适配的底层容器必须拥有头尾删除的操作,比如list和deque,它们都有在双端删除、插入的函数。而vector则不行,因为vector没有push_front和pop_front。即没有双端插入、删除元素的能力。
template<class T, class container = deque<T>>
class queue
{
public:
queue(const container& copy_con = container())
:_con(copy_con) {;}
void push(const T& val) { _con.push_back(val); }
void pop() { _con.pop_front(); }
const T& front()const { return _con.front(); }
T& front() { return _con.front(); }
bool empty() { return _con.empty(); }
size_t size() { return _con.size(); }
private:
container _con;
};
priority_queue
priority_queue是一个很特殊的适配器,和stack与queue不同,priority拥有属于自己的算法,即堆算法,priority的底层容器虽然用的是vector和deque,但是实际上用的其实是heap,即堆。heap并不是STL中的容器,或者说它是一个只藏在算法中的容器。在一些STL的一些算法中可以看到heap的影子。
博主在这里并不像介绍堆算法,因此很早之间博主就已经实现过了,在这篇博客当中,博主更希望读者能够对适配器有一定的了解。
因此我们直接放上源代码吧。
template<class T,class container=deque<T>,class comp=less<T>>
class priority_queue;
priority_queue存在一个目前尚未了解的STL组件,仿函数,博主将会不就得将来给大家带来仿函数的讲解,我们先在priority_queue当中一睹芳容。
这里仿函数传递的是STL中定义的仿函数less,其作用是生成一个大堆,如果想要priority_queue的底层算法用的是小堆,可以传入仿函数greater。
priority_queue的译名是优先级队列,在priority_queue当中插入的元素会按照某种规则排序(即大小堆的排序规则)。当我们pop掉一个元素时,priority_queue会将内部元素重新排列,以保持pop的元素总是队列中的最大值或最小值。(关于heap的数据结构,博主会在文末放上链接)
template<class T,class container=deque<T>,class comp=less<T>>
class priority_queue
{
public:
priority_queue()
:_con(container()) {;}
template<class inputiterator>
priority_queue(inputiterator first, inputiterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
create_heap();
}
void push(const T val)
{
_con.push_back(val);
adjustup(_con.size()-1);
}
void pop()
{
swap(_con[0], _con.back());
_con.pop_back();
adjustdown(0);
}
const T& top()const { return _con.front(); }
T& top() { return _con.front(); }
size_t size(){ return _con.size(); }
bool empty() { return _con.empty(); }
private:
void create_heap()//向下调整建堆算法
{
for (int i = (_con.size() - 2) / 2; i >= 0; i--)
{
adjustdown(i);
}
}
void adjustdown(int parent)//向下调整算法
{
int child=parent*2+1;
while (child < _con.size())
{
if (child + 1 < _con.size() && compare(_con[child], _con[child + 1]))
{
child++;
}
if (compare(_con[parent], _con[child]))
{
std::swap(_con[parent],_con[child]);
}
else
break;
parent=child;
child = parent * 2 + 1;
}
}
void adjustup(int child)//向上调整算法
{
int parent=(child-1)/2;
while (parent != child)
{
if (compare(_con[parent] , _con[child]))
{
std::swap(_con[parent], _con[child]);
}
else
break;
child = parent;
parent = (child - 1) / 2;
}
}
container _con;
comp compare;
};
}