stack的文档详解
stack的使用
stack()
构造空的栈
empty()
检测stack是否为空
size()
返回stack中元素的个数
top()
返回栈顶元素的引用
push()
将元素val压入stack中
pop()
将stack中尾部的元素弹出
queue的文档详解
queue的使用
queue()
构造空的队列
empty()
检测队列是否为空,是返回true,否则返回false
size()
返回队列中有效元素的个数
front()
返回队头元素的引用
back()
返回队尾元素的引用
push()
在队尾将元素val入队列
pop()
将队头元素出队列
priority_queue的文档详解
注意
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。
1、priority_queue()/priority_queue(first,last) 构造一个空的优先级队列
2、empty( )检测优先级队列是否为空,是返回true,否则返回false
3、top( ) 返回优先级队列中最大(最小元素),即堆顶元素
4、push(x) 在优先级队列中插入元素x
5、pop() 删除优先级队列中最大(最小)元素,即堆顶元素
#include
#include
#include // greater算法的头文件
void TestPriorityQueue()
{
// 默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{ 1,2,5,3,6,8,0,9 };
priority_queue<int> q1;
for (auto& e : v)
q1.push(e);
cout << q1.top() << endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2022, 8, 6));
q1.push(Date(2022, 1, 2));
q1.push(Date(2022, 1, 3));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2022, 1, 9));
q2.push(Date(2022, 1, 8));
q2.push(Date(2022, 1, 10));
cout << q2.top() << endl;
}
#include
namespace bite
{
template<class T>
struct less
{
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
template<class T>
struct greater
{
bool operator()(const T& left, const T& right)
{
return left > right;
}
};
template<class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
// 创造空的优先级队列
priority_queue() : c() {}
template<class Iterator>
priority_queue(Iterator first, Iterator last)
: c(first, last)
{
// 将c中的元素调整成堆的结构
int count = c.size();
int root = ((count - 2) >> 1);
for (; root >= 0; root--)
AdjustDown(root);
}
void push(const T & data)
{
c.push_back(data);
AdjustUP(c.size() - 1);
}
void pop()
{
if (empty())
return;
swap(c.front(), c.back());
c.pop_back();
AdjustDown(0);
}
size_t size()const
{
return c.size();
}
bool empty()const
{
return c.empty();
}
// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
const T& top()const
{
return c.front();
}
private:
// 向上调整
void AdjustUP(int child)
{
int parent = ((child - 1) >> 1);
while (child)
{
if (Com()(c[parent], c[child]))
{
swap(c[child], c[parent]);
child = parent;
parent = ((child - 1) >> 1);
}
else
{
return;
}
}
}
// 向下调整
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < c.size())
{
// 找以parent为根的较大的孩子
if (child + 1 < c.size() && Com()(c[child], c[child + 1]))
child += 1;
// 检测双亲是否满足情况
if (Com()(c[parent], c[child]))
{
swap(c[child], c[parent]);
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
private:
Container c;
};
}
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和queue、只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。
简单来说它的出现是想融合vector和list,但是两方面都没有达到极限。但还是有一定的用武之地。
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂。
vector优点:
支持下标随机访问,尾插尾删效率高,cpu高速缓存命中高。
vector缺点:
因为是静态数组,所以当空间不够,一般进行2倍的扩容,扩容可能导致空间浪费,频繁扩容也有效率的损失,而且头插头删效率极低。
list优点:
按需申请释放空间,任意位置O(1)插入删除
list缺点:
不支持下标随机访问,且需要额外存储连接关系的指针
为什么stack默认使用deque不用vector or list?
与vector比较,deque的优势是:头部插入和删除时,不需要移动元素,效率特别高,而且在扩容时,也不需要拷贝大量的元素,因此其效率是比vector高的。
与list比较,deque的优势是:其底层是连续空间,空间利用率比较高,不会频繁申请小块空间,申请释放空间次数少代价低,不需要存储额外字段,cpu缓存命中高。
为什么queue默认使用deque不用list?
同上。
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
总结:
deque适合头尾的插入和删除,但是中间插入删除和随机访问效率不是太好,如果要高频随机访问,还是有老大哥vector,想要任意位置插入删除还得是老大哥list。
namespace zkr
{
template<class T, class Con = deque<T>>
//template>
//template>
class stack
{
public:
stack() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
}
namespace zkr
{
template<class T, class Con = deque<T>>
//template>
class queue
{
public:
queue() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& back() { return _c.back(); }
const T& back()const { return _c.back(); }
T& front() { return _c.front(); }
const T& front()const { return _c.front(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
}
⭐感谢阅读,我们下期再见
如有错 欢迎提出一起交流