C++: stack and queue, priority queue (+ functor, reverse iterator, typename)

Table of contents

1. Introduction to stacks, queues, and priority queues

2.Adapter

2. Stack and queue, use of priority queue

3. Questions related to stacks and queues

1.155. Minimum stack

2. Stack push and pop sequence

3.150. Evaluating reverse Polish expressions

4. Simulation implementation

1.stack

2.queue

3.Priority queue priority_queue

Common mistakes:

focus

(1) The reason why top() returns type const T&

 (2) Functor #include

priority_queue code

4. Comparison between deque, vector and list

vector:

list:   

therefore: #include

5. Summary:

5. Functor

1.

2. Functor questions

3. Example 2: A native pointer to an array is itself a natural iterator

6. Reverse iterator

7.typename


1. Introduction to stacks, queues, and priority queues

2.Adapter

Adapter is a design pattern ( a design pattern is a set of code design experiences that are repeatedly used, known to most people, classified and cataloged) . This pattern converts the interface of a class into another one that the customer wants. interface .

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>

deque (double-ended queue ) : It is a double-opening " continuous " space data structure . The meaning of double-opening is that insertion and deletion operations can be performed at both ends, and the time complexity is O(1) , which is the same as Compared with vector , header insertion is more efficient and does not require moving elements; compared with list , space utilization is relatively high.

Deque is not a truly continuous space, but is made up of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array .

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.


Guess you like

Origin blog.csdn.net/zhang_si_hang/article/details/126067468