1.stack和queue
stack:栈
queue:队列
两者都是容器适配器
通过容器的push_back等功能来实现
容器适配器 都不支持迭代器遍历 因为他们通常都包含一些特殊性质
如果支持迭代器随便遍历 那他们无法很好的保持他的性质
2.stack/queue的实现/使用
#include <vector>
#include <list>
#include <iostream>
#include <queue>
#include <functional>
using namespace std;
namespace szh
{
template<class T,class Container>//容器的类型 这个Container我也不知道 传什么就用什么
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T& top()
{
return _con.back();
}
private:
Container _con;
};
void test_stack()
{
//stack<int, vector<int>> st;//利用模板传vector
//stack<int, list<int>> st;//利用模板传list 两者都可以实现
stack<int, deque<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
}
- 模板中定义一个Container的参数 用来表示容器
- 这样就可以利用模板传容器
- 直接调用容器的函数
- 所有的内置功能都是容器的相关函数
queue同理
#include <vector>
#include <list>
#include <iostream>
#include <deque>
using namespace std;
namespace szh
{
template<class T, class Container>//容器的类型 这个Container我也不知道 传什么就用什么
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
private:
Container _con;
};
void test_queue()
{
//queue<int, vector<int>> q;//vector不行 库里面的vector不支持头删 时间复杂度太高
//queue<int, list<int>> q;
queue<int, deque<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中的stack和queue是通过容器适配转换出来的 不是原生实现的 为什么呢? ->
复用 自己写一个容器适配也是可以的 所以没有写死
库里面是用deque来适配的
3.deque
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾
两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬
移元素;与list比较,空间利用率比较高。
- 看起来好像可以替代vector和list的容器 但是实际呢?
排序效率相比vector较慢 不完美
下面测试一下效率
void dvcompare()
{
deque<int> d;
vector<int> v;
const int n = 100000;
int* a1 = new int[n];
int* a2 = new int[n];
srand(time(0));
for (size_t i = 0; i < n; ++i)
{
int x = rand();
d.push_back(x);
v.push_back(x);
}
size_t begin1 = clock();
sort(d.begin(), d.end());
size_t end1 = clock();
size_t begin2 = clock();
sort(v.begin(), v.end());
size_t end2 = clock();
cout << end1 - begin1 << endl;
cout << end2 - begin2 << endl;
//差了四倍左右
//deque慢
//vector快
//所以deque替代不了vector 效率低了
}
使用
void test_deque()
{
deque<int> d;
d.push_back(-1);
d.push_back(1);
d.push_back(2);
d.push_back(5);
d.push_back(0);
d.push_front(10);
for (size_t i = 0; i < d.size(); ++i)
{
cout << d[i] << " ";
}
cout << endl;
}
- deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。
- 双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂
- 上面谈到效率不如vector 为什么要用deque?那为什么栈和队列的适配又可以使用呢?
- 头尾的插入删除效率还是可以的 stack和queue没有用到随机访问 所以可以用
- deque就是双端开口 可进可出
实际上deque的结构 如下图
-
设计4个指针 类似小数组的组合
-
如何管理多个小数组buffer呢?
通过中控映射的方式 中控管理的指针数组 每个指针指向一个小数组
-
如何实现opeartor[]的随机访问呢?
通过指针的移动来访问数据
需要计算访问的第i个数据在哪个buffer
当数据量大了以后 效率就变低了
4.priorty_queue
优先级队列 底层就是堆 默认是大堆 但是缺省符号给了小于 小堆需要改变符号 变成大于
那么默认是大堆 如果想建小堆 那么需要一个一个换符号 很麻烦
这时模板的优势就来了 再增加一个模板参数 控制大于号还是小于号
//默认升序 怎么排降序 通过仿函数比较大小
void test_sort()
{
vector<int> v;
v.push_back(5);
v.push_back(1);
v.push_back(2);
v.push_back(4);
//升序less <
sort(v.begin(), v.end());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//降序 greater >
//greater<int> gt;
//sort(v.begin(), v.end(), gt);
sort(v.begin(), v.end(), greater<int>());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test_priority_queue()
{
//priority_queue<int> pq;//默认大的优先级高 //默认是大堆
priority_queue<int,vector<int>,greater<int>> pq;//如果想变成小的优先级高 如何操作? 仿函数 #include <functional>
pq.push(3);
pq.push(1);
pq.push(9);
pq.push(4);
pq.push(2);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
#include <iostream>
#include <vector>
using namespace std;
namespace szh
{
//默认是大堆
template<class T, class Container = vector<T>,class Compare = less<T>>//标准库默认是大堆 但是缺省给了小于
class priority_queue//默认vector 底层是堆 logN 二叉树
{
public:
//从小到大就是符号变一下 其他都一样 有没有很好的方式取控制符号?
template<class T>
struct less
{
//仿函数 函数对象
bool operator()(const T& x1, const T& x2)
{
return x1 < x2;
}
};
template<class T>
struct greater
{
//仿函数 函数对象
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
//向上调整
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)//child==0的时候就不用调了
{
//if (_con[child] > _con[parent])
if (com(_con[parent], _con[child]))//换顺序 默认是小于
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整
void AdjustDown(int root)
{
Compare com;
int parent = root;
int child = parent * 2 + 1;
while (child < _con.size())
{
//选出左右孩子中大的那一个
//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))//换顺序 默认是小于号
{
++child;
}
//if (_con[child] > _con[parent])
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);//向上调整 二叉树处
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);//向下调整
}
T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void test_priority_queue()
{
//priority_queue<int> pq;//大堆
priority_queue<int,vector<int>,greater<int>> pq;//大于反而是小堆
pq.push(3);
pq.push(1);
pq.push(9);
pq.push(4);
pq.push(2);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
}
5.总结
-
容器:string/vector/list/deque(序列式容器)
-
适配器:stack/queue/priority_queue
-
迭代器:iterator/const_iterator/reverse_iterator/const_reverse_iterator
-
算法:sort/find/reverse
-
仿函数:less/greater
-
deque的缺点:
大量的频繁opeartor[]的效率低
迭代器的遍历相对复杂 效率也有一些影响
-
vector缺点:
头插头删的效率低 空间不够用了增容代价大
-
list缺点:
不支持随机访问
-
priority_queue 优先级队列 大的小的谁优先级高是看场景的
#include<queue> 包了队列和优先级队列
-
面试问题:能否设计出一个数据结构 解决vector和list的缺点
deque中控映射指针数组不够了增容就可以
但对比vector 一起去堆相同的10w个数据排序 deque效率相比vector差了4-5倍
【C++】10.stack和queue 完