stack类和queue类
stack
和queue
以及priority_queue
(优先级队列)是STL中三大容器适配器,将其称为容器适配器是因为其在底层只是对现有容器进行的了封装而并没有重新实现。因此在容器适配器中都有让传入容器的模板参数。
容器适配器
适配器是一种设计模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。而容器适配器就是将常见容器的接口进行封装,使之成为我们需要的结构。因此在容器适配的模板参数中我们可以传入我们想要使用的容器作为模板参数进行封装,但是作为适配器的实现容器它们也需要满足一定的要求才可以作为模板参数。1
2
3
4
5//Container就是底层实现的容器的模板参数
template <class T, class Container = deque<T> > class queue;
template <class T, class Container = deque<T> > class stack;
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
stack类
stack
和我们数据结构中的栈实现了同样的功能,后进先出是其最大的特点。它只能从容器的一端进行元素的插入和提取,来满足栈的相关功能。
它底层的容器可以是标准中的容器类也可以是其他的容器类,但是无论如何这些容器类必须满足以下要求。
1、empty()
,判空操作。
2、back()
,获取尾部元素。
3、push_back()
,尾插。
4、pop_back()
,尾删。
满足这些要求的容器类才可以被传入,如果没有传入容器的话则默认使用deque
。
常用接口
1 | stack(const container_type &ctnr = container_type()); //构造空的栈 |
栈的应用
最小栈
力扣:
https://leetcode-cn.com/problems/min-stack/submissions/
这道题就是用栈区实现一个可以返回当前栈中最小值的栈,方法有很多种,这里只提供一种方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50class MinStack {
public:
/** initialize your data structure here. */
MinStack()
:_min(INT_MAX)
{}
void push(int x) {
if(x < _min)
{
_min = x;
}
_elem.push_back(x);
}
void pop() {
if(top() == _min)
{
_min = INT_MAX;
for(int i = 0; i < _elem.size() - 1; i++)
{
if(_elem[i] < _min)
{
_min = _elem[i];
}
}
}
_elem.pop_back();
}
int top() {
return _elem[_elem.size() - 1];
}
int getMin() {
return _min;
}
private:
std::vector<int> _elem;
int _min;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
栈的压入、弹出序列
牛客网:
https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
这道题用到了栈的弹出序列是否匹配压入序列的算法,大致思路是用一个栈进行模拟,并且设立用于分别遍历弹出和压入序列的下标,只要栈顶元素不等于当前遍历到的弹出序列的元素,就将压入序列当前元素压入栈,并且遍历压入序列的下一个元素,如果相等,则将栈顶元素弹出,并且遍历弹出序列的下一个元素。如果当弹出序列还没有遍历完毕而压入序列也始终找不到下一个可以和弹出序列进行匹配的元素则不匹配,返回false,如果弹出序列遍历完毕,则表示匹配,返回true。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
int pushIndex = 0;
int popIndex = 0;
//用栈进行模拟
stack<int> s;
//遍历弹出序列
while(popIndex < popV.size())
{
//栈为空或者栈顶元素不等于当天弹出序列遍历到的元素
while(s.empty() || s.top() != popV[popIndex])
{
//压入序列还有元素则压入
if(pushIndex < pushV.size())
{
s.push(pushV[pushIndex]);
pushIndex++;
}
//压入序列全部压入无法完成匹配
else
{
return false;
}
}
//栈顶元素和弹出序列当前元素相同则往后继续遍历
s.pop();
popIndex++;
}
return true;
}
};
逆波兰表达式求值
力扣:
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/submissions/
逆波兰表达式也成为后缀表达式,这里的逆波兰表达式求值由于不涉及括号所以比较简单,我们只需要将遇到的操作数放入栈,遇到操作符则取出栈顶两个元素进行计算,然后将结果再次放入栈最后取出栈中最后一个元素就是答案。不过这里要注意负数的处理,以及从栈中取出的两个操作数哪个是前操作数哪个是后操作数的问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
for(int i = 0; i < tokens.size(); i++)
{
if(tokens[i][0] <= '9' && tokens[i][0] >= '0')
{
s.push(atoi(tokens[i].c_str()));
}
//要处理负数
else if(tokens[i][0] == '-' && tokens[i][1] != '\0')
{
string str = tokens[i].substr(1);
int temp = -1 * atoi(str.c_str());
s.push(temp);
}
else
{
int num1 = s.top();
s.pop();
int num2 = s.top();
s.pop();
switch(tokens[i][0])
{
case '+':
s.push(num2 + num1);
break;
case '-':
s.push(num2 - num1);
break;
case '*':
s.push(num2 * num1);
break;
case '/':
s.push(num2 / num1);
break;
}
}
}
return s.top();
}
};
queue类
和stack
相反,queue
有着先进先出的特性,这使得他能够按照顺序完成某一功能。它从容器的一端插入另一端删除,因此它的模板参数中的容器要求有以下功能:
1、empty()
,判空
2、size()
,长度
3、back()
,获取尾部元素
4、front()
,获取头部元素
5、push_back()
,尾插
6、pop_front()
,头删
如果没有传入容器的话则默认使用deque
。
常用接口
1 | queue(const container_type &ctnr = container_type()); //构造空的队列 |
队列的应用
用队列实现栈
力扣:
https://leetcode-cn.com/problems/implement-stack-using-queues/
利用栈进行层序遍历类似于bfs,即广度优先搜索,我们在进行遍历时需要将每一个结点的儿子都压入栈,只要栈不为空就一直遍历下去。但是这个题要求要区分每一层,则我们需要在遍历每一层前都拿到当前队列中结点的个数,则是这一层要遍历的结点数,我们这一层就只遍历这些结点即可。我们甚至由此可以得出N叉树的层序遍历,都可以用类似的思路。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
queue<TreeNode*> q;
//根结点为空直接返回
if(root == nullptr)
{
return res;
}
q.push(root);
while(!q.empty())
{
vector<int> line;
int size = q.size();
for(int i = 0; i < size; i++)
{
TreeNode* node = q.front();
if(node->left != nullptr)
{
q.push(node->left);
}
if(node->right != nullptr)
{
q.push(node->right);
}
q.pop();
line.push_back(node->val);
}
res.push_back(line);
}
return res;
}
};
priority_queue类
priority_queue
称为优先级队列,它的出队元素永远是队列中优先级最高的那一个,它的底层是用一个堆来完成的,堆顶就是优先级最高的元素。
优先级队列在每次插入删除元素后都会分别调用算法push_heap()
和pop_heap()
重新建堆,并且还会和容器中的push_back()
和pop_back()
结合。同时优先级队列中有一个Compare
的模板参数,用于传入一个仿函数,建堆会用这个仿函数按照严格弱序进行建堆。
所谓仿函数则是一个类中重载了operator()
使其实例化的对象可以像函数一样进行调用,它的特点是可以使类作为模板参数一部分传入模板内,并且使其实例化出的对象可以当作函数使用。标准中给了两个类提供了仿函数的功能,less
和greater
。
如果在优先级队列中存放自定义类型的数据,要求自定义类型必须重载operator<
或operator>
取决于Compare
的类型,这两个重载在仿函数中将会进行调用用于判断和比较。
同样的优先级队列中的容器也必须满足一些要求才能完成功能:
1、empty()
,判空
2、size()
,长度
3、front()
,获取头部元素
4、push_back()
,尾插
5、pop_back()
,尾删
容器还需要支持随机访问迭代器,以便始终在内部保持堆结构。
为什么是这些接口?front()
用于在建堆和堆调整后可以获得堆顶元素,即优先级最高的元素,即top()
;push()
在给堆插入元素时是从容器的尾部插入,因此需要push_back()
进行尾插,尾插后再用push_heap()
调整堆;pop()
在出队时要先调用pop_heap()
,这个接口会将堆顶元素与堆的最后一个元素交换位置,并且对堆的除最后一个元素进行调整,这个过程类似于堆排序。之后再调用容器的pop_back()
即可将原本堆顶优先级最高的元素出队。
常用接口
1 | priority_queue(const Compare &x = Compare(), |
默认情况下,类模板中Compare
的类型为less
,实例化出来的对象重载时判断的为是否小于,因此会创建大堆,队列出队返回的值也是队中优先级最高的。
优先级队列的应用
优先级队列底层是堆的实现,因此优先级队列擅长于结局堆能够解决的问题,例如TopK问题。
数组中的第K个最大的元素
力扣:
https://leetcode-cn.com/problems/kth-largest-element-in-an-array/
这道题就利用优先级队列找到第k个大的元素即可,也可以先排序后再找。1
2
3
4
5
6
7
8
9
10
11class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> que(nums.begin(), nums.end());
for(int i = 0; i < k - 1; i++)
{
que.pop();
}
return que.top();
}
};
前K个高频元素
力扣:
https://leetcode-cn.com/problems/top-k-frequent-elements/
这道题做起来不难,但是是对优先级队列较为综合的运用,我们要考虑到TOPK问题中找前K大要用小根堆,还要考虑到要将出现次数与元素本身联系起来要在优先级队列中存储pair
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int, int> m;
for(int i = 0; i < nums.size(); i++)
{
m[nums[i]]++;
}
//topK问题求前K大一定要用小根堆,维护K个数据
//用大根堆要放入全部数据费时费空间
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> que;
for(auto e : m)
{
if(que.size() >= k)
{
if(e.second > que.top().first)
{
que.pop();
que.push(make_pair(e.second, e.first));
}
}
else
{
que.push(make_pair(e.second, e.first));
}
}
vector<int> res(k);
while(!que.empty())
{
res[que.size() - 1] = que.top().second;
que.pop();
}
return res;
}
};
实现
依旧是实现常用接口,了解底层原理。
stack类的实现
1 | #pragma once |
queue的实现
1 | #pragma once |
priority_queue的实现
在SGI版本的priority_queue
中建堆以及调整堆都是调用的algorithm
中的算法函数完成的,但是在模拟实现中我们完全自己实现。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100#pragma once
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
template<class T>
struct Less
{
bool operator()(const T& obj1, const T& obj2)
{
return obj1 < obj2;
}
};
template<class T>
struct Greater
{
bool operator()(const T& obj1, const T& obj2)
{
return obj1 > obj2;
}
};
template<class T, class Container = std::vector<T>, class Compare = Less<T>>
class Priority_queue
{
public:
//向下调整
void AdjustDown(size_t parent)
{
size_t child = parent * 2 + 1;
Compare com;
while(child < _con.size())
{
if(child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++;
}
if(com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//向上调整
void AdjustUp(size_t child)
{
size_t parent = (child - 1) / 2;
Compare com;
while(child > 0)
{
if(com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Push(const T& data)
{
_con.push_back(data);
//模拟push_heap()的功能
AdjustUp(_con.size() - 1);
}
void Pop()
{
//模拟pop_heap()的功能
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
size_t Size()
{
return _con.size();
}
bool Empty()
{
return _con.empty();
}
T& Top()
{
return _con.front();
}
const T& Top() const
{
return _con.front();
}
//private:
Container _con;
};