C++:栈与队列,优先级队列(+仿函数,反向迭代器,typename)

目录

一.栈与队列,优先级队列的介绍

2.适配器

二.栈与队列,优先级队列的使用

三.栈和队列相关题目

1.155. 最小栈

2.栈的压入、弹出序列

3.150. 逆波兰表达式求值

四.模拟实现

1.stack

2.queue

3.优先级队列 priority_queue

易错点:

重点

(1)top()返回类型    const T& 的原因

 (2)仿函数 #include

priority_queue代码

4.deque与vector,list对比

vector:

list:   

deque:#include

5.总结:

五.仿函数

1.

2.仿函数题目

3.例子2:指向数组的原生指针,本身就是天然迭代器

六.反向迭代器

七.typename


一.栈与队列,优先级队列的介绍

2.适配器

适配器是一种设计模式 ( 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结) 该种模式是将一个类的接口转换成客户希望的另外一个接口

二.栈与队列,优先级队列的使用

优先级队列的问题:优先级队列默认大的优先级高,传的是less仿函数,底层是一个大堆;
想控制小的优先级高,传greater仿函数,底层是一个小堆。这个反过来的,算是设计的一个失误。

(实际上less——大堆,greater——小堆,greater可理解为小堆下面越来越大)

#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;
}

三.栈和队列相关题目

1.155. 最小栈

思路:用两个栈,st存放入的数据,minst存放入数据后的最小数据;

优化:minst存放入数据后的最小数据,如果后面再尾插的数据都没有这个数据大,minst就不尾插数据,如果尾插的数据 小于或等于 minst中最后一个数据,就尾插这个数据进minst。

举例:st第一个数据5,minst尾插一个5;st尾插8,8>5,minst不变;st尾插7,minst不尾插;st尾插2,2<5,尾插2;st尾插1,1<2,minst尾插1;st又尾插1,1=1,minst还要尾插1。(因为如果=时minst不尾插,pop数据时,st尾删一个1,minst就一个1,尾删后,st中还有一个1,最小值就乱了)

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.栈的压入、弹出序列

栈的压入、弹出序列_牛客题霸_牛客网 (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. 逆波兰表达式求值

过程:逆波兰表达式又称后缀表达式

  手敲:

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();
    }
};

四.模拟实现

1.stack

底层是vector,list,deque都可以(如果是string的话会截断)

deque是双端队列(顺序表),适合头尾插入删除,不适合中间插入删除,不适合随机访问(虽然支持,但不适合随机访问)

#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

底层只能是list,或deque,因为vector不支持头删

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

易错点:

Compare仿函数用法,向上下调整函数要传参,AjustDown函数忘记过程,pop函数忘记断言不为空,仿函数内()重载忘加const

重点

(1)top()返回类型    const T& 的原因

如果T是string,则返回_con[0] 又会有拷贝构造,为了提高效率就加上引用,但加上引用就可以被任意修改,防止原数组被修改,就加上const


		const T& top()    //const T&类型的原因,详情见(1)
		{
			return _con[0];
		}

 (2)仿函数 #include <functional>

就是一个类重载了运算符(),并且这个类less或greater还是一个空类,Compare comFunc; 直接定义出一个对象,comFunc(_con[parent], _con[child]) 使用起来像一个函数一样。(变量comFunc 没必要放在成员变量中,因为对象的类是空类,创建的成本小)

 // 仿函数/函数对象 -- 对象可以像调用函数一样去使用
    /*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代码

#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.deque与vector,list对比

vector:

优点:
适合尾插尾删,随机访问,cpu高速缓存命中高
缺点:
a、不适合头部或者中部插入删除,效率低,需要挪动数据
b、扩容有一定性能消耗,还可能存在一定程度的空间浪费。

list:   

优点:
a.任意位置插入删除效率高。O(1) b、按需申请释放空间。
缺点:
不支持随机访问
cpu高速缓存命中低


deque:#include<deque>

deque (双端队列 ) :是一种双开口的 " 连续 " 空间的数据结构 ,双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1) ,与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。

deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维 数组

根据vector和list缺点升级而成,虽然deque优点均衡但是不够极致,vector的随机访问效率最高很极致,list任意插入功能机制

优点:
a、头部和尾部插入删除数据效率不错
b、支持随机访问。
C、扩容代价小。
d、cpu高速缓存命中高
缺点:
a、中部插入删除效率不行
b、虽然支持随机访问,但是效率相比vector而言还是有差距,频繁随机访问要小心

deque适用场景:大量头尾插入删除,偶尔随机访问(说的就是栈和队列)。stack,queue用deque作为默认适配容器是ok的

5.总结:

stack底层:deque,vector,list(尾插尾删都支持)

queue:deque,list(vector不支持头插头删)

priority_queue:vector,deque(deque不推荐,list不支持随机访问)

五.仿函数

仿函数本质不是函数,而是一个对象,是一个像函数一样可以使用的对象,即:对象可以像调函数一样去使用

仿函数 就是一个类重载了运算符(),并且这个类less或greater还是一个空类,Compare comFunc; 直接定义出一个对象,comFunc(_con[parent], _con[child]) 使用起来像一个函数一样。(变量comFunc 没必要放在成员变量中,因为对象的类是空类,创建的成本小)

 

仿函数可以传类型或对象

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.仿函数题目

仿函数比起一般函数具有很多优点,以下描述错误的是( )

A.在同一时间里,由某个仿函数所代表的单一函数,可能有不同的状态

B.仿函数即使定义相同,也可能有不同的类型

C.仿函数通常比一般函数速度快

D.仿函数使程序代码变简单

答:

A.仿函数是模板函数,可以根据不同的类型代表不同的状态

B.仿函数是模板函数,可以有不同类型

C.仿函数是模板函数,其速度比一般函数要慢,故错误

D.仿函数在一定程度上使代码更通用,本质上简化了代码,选C

3.例子2:指向数组的原生指针,本身就是天然迭代器

	    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());    //按销量排降序
	}

六.反向迭代器

反向迭代器跟正向迭代器相比,除了++/--时方向相反,其他操作基本一致

自己模拟实现的:

 反向迭代器代码放在 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;
	}

七.typename

类模板模板虚拟类型没有实例化之前不能去他里面找内嵌定义的类型
类模板没有实例化,找出来也是虚拟类型,后期无法处理
T&
typename告诉编译器后面这一串是一个类型,等Iterator实例化以后,
你再去他里面去找这个内嵌类型

只要取类模板里面的内嵌类型都要加 typename


猜你喜欢

转载自blog.csdn.net/zhang_si_hang/article/details/126067468