Table of contents
1. Introduction to stacks, queues, and priority queues
2. Stack and queue, use of priority queue
3. Questions related to stacks and queues
2. Stack push and pop sequence
3.150. Evaluating reverse Polish expressions
3.Priority queue priority_queue
(1) The reason why top() returns type const T&
4. Comparison between deque, vector and list
3. Example 2: A native pointer to an array is itself a natural iterator
1. Introduction to stacks, queues, and priority queues
2.Adapter
2. Stack and queue, use of priority queue
Problems with the priority queue: By default, the larger priority queue has a higher priority, and the less functor is passed, and the bottom layer is a large heap; if you want to
control the small priority, the greater functor is passed, and the bottom layer is a small heap. This, in turn, is a design error.
(In fact, less means a big pile, greater means a small pile, and greater can be understood as the small pile getting bigger and bigger)
#include<iostream>
#include<stack>
#include<queue>
#include <functional>
using namespace std;
namespace std
{
void test_stack() //栈
{
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
void test_queue() //队列
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
void test_priority_queue() //优先级队列
{
//priority_queue<int> pq;
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(2);
pq.push(5);
pq.push(1);
pq.push(6);
pq.push(8);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
}
int main()
{
std::test_stack();
std::test_queue();
std::test_priority_queue();
return 0;
}
3. Questions related to stacks and queues
1. 155. Minimum stack
Idea: Use two stacks, st stores the incoming data, and minst stores the minimum data after the incoming data;
Optimization: Minst stores the minimum data after the data is inserted. If the data inserted later is not as large as this data, minst will not insert the data at the end. If the data inserted at the end is less than or equal to the last data in minst, this data will be inserted at the end . Enter minst.
For example: the first data of st is 5, insert a 5 at the end of minst; insert 8 at the end of st, 8>5, minst remains unchanged; insert 7 at the end of st, but minst does not insert at the end; insert 2 at the end of st, 2<5, insert 2 at the end ; Insert 1 at the end of st, 1<2, insert 1 at the end of minst; insert 1 at the end of st, 1=1, and insert 1 at the end of minst. (Because if = does not insert the tail of minst, when popping data, a 1 will be deleted from the tail of st, and minst will have a 1. After the tail is deleted, there will be a 1 in st, and the minimum value will be messed up)
class MinStack {
public:
MinStack() {
//成员变量是自定义类型,默认生成够用
}
void push(int val) {
_st.push(val);
if(_minst.empty()||val<=_minst.top())
{
_minst.push(val);
}
}
void pop() {
if(_st.top()==_minst.top())
{
_minst.pop();
}
_st.pop();
}
int top() {
return _st.top();
}
int getMin() {
return _minst.top();
}
private:
stack<int> _st;
stack<int> _minst;
};
2. Stack push and pop sequence
Stack push and pop sequence_NiukeTiba_Niuke.com (nowcoder.com)
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
stack<int> st;
int popi=0;
for(auto e: pushV)
{
st.push(e);
while(!st.empty() && st.top()==popV[popi])
{
popi++;
st.pop();
}
}
return st.empty();
}
};
3. 150. Evaluating reverse Polish expressions
Process: Reverse Polish expression, also known as suffix expression
Hand knocking:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto e:tokens)
{
if(e=="+"||e=="-"||e=="*"||e=="/")
{
int b=st.top(); //易错点1
st.pop();
int a=st.top();
st.pop();
switch(e[0])
{
case '+':
st.push(a+b);
break;
case '-':
st.push(a-b);
break;
case '*':
st.push(a*b);
break;
case '/':
st.push(a/b);
break;
default:
break;
}
}
else
{
st.push(stoi(e));
}
}
return st.top();
}
};
4. Simulation implementation
1.stack
The bottom layer can be vector, list, or deque (if it is a string, it will be truncated)
Deque is a double-ended queue (sequential table), suitable for head and tail insertion and deletion, not suitable for middle insertion and deletion, and not suitable for random access (although it is supported, it is not suitable for random access)
#pragma once
namespace bit
{
template<class T, class Container = deque<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
const T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void test_stack()
{
//stack<int> s;
stack<int, vector<int>> s;
//stack<int, list<int>> s;
//stack<int, string> s; // ڽضݶʧ
s.push(1);
s.push(2);
s.push(3);
s.push(4);
s.push(300);
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
}
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <list>
#include <deque>
#include <functional>
#include <assert.h>
using namespace std;
#include "Stack.h"
#include "Queue.h"
#include"stack.h"
int main()
{
bit::test_stack();
return 0;
}
2.queue
The bottom layer can only be list or deque, because vector does not support header deletion.
namespace bit
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
const T& front()
{
return _con.front();
}
const T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void test_queue()
{
//queue<int> q;
queue<int, list<int>> q;
//queue<int, vector<int>> q; // 不行
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
}
3.Priority queue priority_queue
Common mistakes:
How to use the Compare functor, you need to pass parameters to adjust the function up and down, the AdjustDown function forgets the process, the pop function forgets to assert that it is not empty, and the () overload in the functor forgets to add const
focus
(1) The reason why top() returns type const T&
If T is a string, _con[0] will be returned and there will be a copy construction. In order to improve efficiency, a reference is added, but by adding a reference, it can be modified arbitrarily. To prevent the original array from being modified, add const
const T& top() //const T&类型的原因,详情见(1)
{
return _con[0];
}
(2) Functor #include <functional>
It means that a class overloads the operator (), and the class less or greater is still an empty class. Compare comFunc; directly defines an object. comFunc(_con[parent], _con[child]) is used like a function. (The variable comFunc does not need to be placed in a member variable, because the class of the object is an empty class and the cost of creation is small)
// 仿函数/函数对象 -- 对象可以像调用函数一样去使用
/*struct less
{
bool operator()(int x, int y)
{
return x < y;
}
};*/
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;
}
};
template<class T, class Container = vector<T>, class Compare = less<T>>//仿函数
class priority_queue
{
public:
void AdjustUp(int child)
{
Compare comFunc; //仿函数
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if (comFunc(_con[parent], _con[child])) //仿函数
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
priority_queue code
#pragma once
namespace bit
{
// 仿函数/函数对象 -- 对象可以像调用函数一样去使用
/*struct less
{
bool operator()(int x, int y)
{
return x < y;
}
};*/
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;
}
};
// 优先级队列 -- 大堆 < 小堆 >
template<class T, class Container = vector<T>, class Compare = less<T>>//仿函数
class priority_queue
{
public:
void AdjustUp(int child)
{
Compare comFunc; //仿函数
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if (comFunc(_con[parent], _con[child])) //仿函数
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void AdjustDown(int parent)
{
Compare comFunc; //仿函数
size_t child = parent * 2 + 1;
while (child < _con.size())
{
//if (child+1 < _con.size() && _con[child] < _con[child+1])
if (child + 1 < _con.size() && comFunc(_con[child], _con[child + 1]))
{ //仿函数
++child;
}
//if (_con[parent] < _con[child])
if (comFunc(_con[parent],_con[child])) //仿函数
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
assert(!_con.empty());
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
const T& top() //const T&类型的原因,详情见(1)
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void test_priority_queue()
{
/*less<int> LessCom;
cout << LessCom(1, 2) << endl;
greater<int> GreaterCom;
cout << GreaterCom(1, 2) << endl;*/
//priority_queue<int> pq;
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(2);
pq.push(5);
pq.push(1);
pq.push(6);
pq.push(8);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
}
4. Comparison between deque, vector and list
vector:
Advantages:
Suitable for tail insertion and tail deletion, random access, high CPU cache hit
Disadvantages:
a. Not suitable for head or middle insertion and deletion, low efficiency, need to move data
b. Capacity expansion has certain performance consumption, and there may be a certain degree of space waste.
list:
Advantages:
a. High efficiency of insertion and deletion at any position. O(1) b. Apply to release space as needed.
Disadvantages:
No support for random access
CPU cache hits are low
deque: #include<deque>
It is upgraded based on the shortcomings of vector and list. Although deque has balanced advantages, it is not extreme enough. Vector has the highest random access efficiency and is extremely efficient. List has an arbitrary insertion function mechanism.
Advantages:
a. Insertion and deletion of data at the head and tail are efficient.
b. Support random access.
C. The expansion cost is small.
d. High CPU cache hit.
Disadvantages:
a. Insertion and deletion efficiency in the middle part is not good.
b. Although random access is supported, the efficiency is still lagging behind vector. Be careful with frequent random access.
Deque is applicable to scenarios: a large number of head-to-tail insertions and deletions, and occasional random access (that is, stacks and queues). It is ok for stack and queue to use deque as the default adaptation container.
5. Summary:
Bottom layer of stack: deque, vector, list (end insertion and end deletion are supported)
queue: deque, list (vector does not support header plug deletion)
priority_queue: vector, deque (deque is not recommended, list does not support random access)
5. Functor
The essence of a functor is not a function, but an object. It is an object that can be used like a function, that is: the object can be used like a function.
A functor is a class that overloads the operator (), and the class less or greater is still an empty class. Compare comFunc; directly defines an object. comFunc(_con[parent], _con[child]) is used like a function. Same. (The variable comFunc does not need to be placed in a member variable, because the class of the object is an empty class and the cost of creation is small)
Functors can pass types or objects
class Solution {
public:
typedef map<string,int>::iterator CountIter;
struct less
{
bool operator()(const pair<string,int>& x,const pair<string,int>& y)
{
return x.second>y.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string,int> countMap;
for(auto& str:words)
{
countMap[str]++;
}
vector<pair<string,int>> v;
CountIter it=countMap.begin();
while(it!=countMap.end())
{
//cout<<it->first<<" "<<it->second<<endl;
v.push_back(*it);
++it;
}
stable_sort(v.begin(),v.end(),less()); //stable_sort稳定排序
vector<string> vv;
for(int i=0;i<k;i++)
{
vv.push_back(v[i].first);
}
for(auto e:v)
{
cout<<e.first<<" "<<e.second<<endl;
}
return vv;
}
};
1.
2. Functor questions
Functors have many advantages over general functions. The following description is wrong ()
A. At the same time, a single function represented by a certain functor may have different states.
B. Functors may have different types even if they are defined the same
C. Functors are usually faster than general functions
D. Functors make program code simpler
answer:
A. Functors are template functions that can represent different states according to different types.
B. Functors are template functions and can have different types
C. Functor is a template function, its speed is slower than ordinary functions, so it is wrong
D. Functors make the code more versatile to a certain extent and essentially simplify the code. Choose C.
3. Example 2: A native pointer to an array is itself a natural iterator
int a[6] = { 1, 2, 5, 2, 5, 7 };
sort(a, a + 6);
sort(a, a + 6, greater<int>());
// 商品
struct Goods
{
string _name;
double _price;
size_t _saleNum;
//...
/*bool operator<(const Goods& g) const
{
return _price < g._price;
}*/
};
struct LessPrice //按价格排序
{
bool operator()(const Goods& g1, const Goods& g2) const
{
return g1._price < g2._price;
}
};
struct GreaterPrice
{
bool operator()(const Goods& g1, const Goods& g2) const
{
return g1._price > g2._price;
}
};
struct LessSaleNum
{
bool operator()(const Goods& g1, const Goods& g2) const
{
return g1._saleNum < g2._saleNum;
}
};
struct GreaterSaleNum
{
bool operator()(const Goods& g1, const Goods& g2) const
{
return g1._saleNum > g2._saleNum;
}
};
void test_functional()
{
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(4);
v.push_back(5);
v.push_back(3);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
// less
sort(v.begin(), v.end());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
// greater
sort(v.begin(), v.end(), greater<int>());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
// 指向数组的原生指针,本身就是天然迭代器
int a[6] = { 1, 2, 5, 2, 5, 7 };
sort(a, a + 6);
sort(a, a + 6, greater<int>());
Goods gds[4] = { { "苹果", 2.1, 1000}, { "香蕉", 3.0, 200}, { "橙子", 2.2,300}, { "菠萝", 1.5,50} };
sort(gds, gds + 4, LessPrice()); //按价格排升序 Less是升序排
sort(gds, gds + 4, GreaterPrice()); //按价格排降序 Greater是降序排
sort(gds, gds + 4, LessSaleNum()); //按销量排升序
sort(gds, gds + 4, GreaterSaleNum()); //按销量排降序
}
6. Reverse iterator
Compared with the forward iterator, the reverse iterator is basically the same except that the direction is opposite when ++/-- is used.
Simulated and implemented by myself:
The reverse iterator code is placed in ReverseIterator.h:
#pragma once
namespace bit
{
// --
template<class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
Iterator _it;
typedef Reverse_iterator<Iterator, Ref, Ptr> Self;
Reverse_iterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
Iterator tmp = _it;
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
Self& operator++()
{
--_it;
return *this;
}
Self& operator--()
{
++_it;
return *this;
}
bool operator!=(const Self& s)
{
return _it != s._it;
}
};
}
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
// 反向迭代器适配支持
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
const_reverse_iterator rbegin() const
{
// list_node<int>*
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(begin());
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
……
对vector中反向迭代器的测试:
void test_vector8()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
// 反向迭代器适配支持
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
const_iterator begin() const
{
// list_node<int>*
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
//return _head->_next;
}
iterator end()
{
return iterator(_head);
}
const_reverse_iterator rbegin() const
{
// list_node<int>*
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(begin());
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
……
对list中反向迭代器的测试:
void test_list7()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
7.typename
Before the virtual type of a class template template is instantiated, you cannot look for the embedded defined type in it. The class
template has not been instantiated, and it is also a virtual type when you find it. It cannot be processed later.
T&
typename tells the compiler that the following string is a type, and waits for the Iterator instance. After it is transformed,
you can go to it to find the embedded type.
As long as you take the embedded type in the class template, you must add typename.