一、适配器
什么是适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
其实就是一种可以用多种容器作为底层来实现的一种容器,比如栈和队列,它们本身不是一种容器,但是它们具有某种特性,可以用基础顺序容器来实现,比如vector和list。
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。可以看一下定义:
例如,我们可以显式传容器
int main()
{
stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
return 0;
}
二、 浅谈deque
从上面可以看到,C++中默认栈和队列是用deque(双端队列),来作为底层容器实现的。那么deque是什么呢,有什么特性呢?
deque首先被弄出来的主要目的就是为了综合vector和list的优势。先来了解一下vector和list各自的优劣势
2.1 vector和list的优劣势
vector
优势:
- 支持高效的随机访问(最主要的优点)
- 尾插尾删效率高,CPU高速缓存命中率高(因为物理空间连续)
劣势
- 空间存在一定浪费,按需申请空间,导致频繁增容,效率低。
- 头插头删需要移动大量的数据,效率低
list
优势
- 按需申请和释放空间 ,空间都被利用。
- 任意点插入和删除效率高,O(1)
劣势 - 不支持随机访问。(最大的劣势)
既然deque想要综合两个的优点,那么deque是什么结构,真的能替代上面两个吗?
2.2 deque基本原理介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示 存储思想是,中控指针数组指向每个固定数组,从数组中间部分指针指向的数组开始存储,如果要头插,就往上一个数组的最后插入,尾插就往后继续插入。仍然不能解决中间插入删除的问题。
deque对比vector和list的优势
deque对比vector:扩容代价不大,不需要拷贝数据浪费空间也不多。 对比list:cpu高速cache命中。其次不会频繁申请小块空间。申请和释放空间次 数少代价低
deque虽然也可以做到随机访问,但是需要的代价比较大。它也不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list。 但是对应栈和队列这两种数据结构是非常适合的,它们不需要随机访问,只支持头尾操作,所以大佬们采用deque作为容器。
三、stack的实现
stack的使用很简单,这里就不在叙述了。直接模拟实现
#pragma once
namespace sqin
{
template<class T, class Container = vector<T>>
class stack
{
public:
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
T& top()
{
return _con.back();
}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
private:
Container _con;
};
void test_stack()
{
stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
}
四、 queue的实现
#pragma once
namespace sqin
{
template<class T, class Container = list<T>>
class queue
{
public:
queue()
{}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
void push(const T& t)
{
_con.push_back(t);
}
void pop()
{
_con.pop_front();
}
private:
Container _con;
};
void test_queue()
{
queue<int, list<int>> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
}
五、priority_queue模拟实现
priority_queue文档
5.1 初识仿函数
STL库里面默认的排序算法是非递减排序,那如果我们要按非递增排序怎么办,不可能再写一个一样的函数,换一下比较符合吧。这个时候就需要仿函数来帮我们完成这件事,传入自己想要的比较方式。
什么是仿函数 仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
例如库里面有大于和小于两个仿函数
5.2 模拟实现priority_queue
定义和测试
#pragma once
namespace sqin
{
template<class T, class Container = std::vector<T>, class Compare = Less<T> >
class priority_queue
{
private:
Container _con;
};
void test_priority()
{
std::vector<int> v = { 2,4,5,6,7,1,3,8 };
priority_queue<int> pq;
pq.push(2);
pq.push(4);
pq.push(5);
pq.push(7);
pq.push(6);
pq.push(1);
std::cout << pq.top() << std::endl;
pq.pop();
pq.pop();
}
}
仿函数
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
template<class T>
struct Greater
{
bool operator()(const T& x, const T& y) const
{
return x > y;
}
};
构造函数
priority_queue()
{}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first,last)
{
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
{
adjust_down(i);
}
}
建堆相关函数
private:
void adjust_up(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
child = child + 1;
if (com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
主要函数
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
T& top()
{
return _con[0];
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
|