• 适配器模式适配出栈和队列及优先级队列


    适配器模式适配出栈和队列及优先级队列

    STL六大组件介绍

    本文主要介绍如何用deque适配出栈和队列,那么deque是什么呢?

    • deque是STL中的一个容器,其具有vector一样的[ ]的重载,实现随机访问,同时也支持链表一样的头插尾插,是结合了vector和list的功能,看似非常牛逼,但实际上也是个纸老虎,其随机访问和头尾插的真实的效率也比不上vector和list,只能是说啥都会但是啥都不精通,因为其结构是由多个数组构成的,由一个指针数组存储每个数组的地址,来进行管理,因此其效率不是很可观。

    img

    deque 与 vector list 的对比

    • 与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

    • 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

    • 但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到
      某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构
      时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作
      为stack和queue的底层数据结构

    img

    deque的成员函数

    与大多数容器一样,deque具有的主要功能有:头尾插入删除,任意位置插入删除,迭代器的支持,获取元素个数,判空等。这里不过多介绍,基本上会用vector或者list就能上手deque的基本使用了。

    请添加图片描述

    用deque适配出stack

    STL中stack的主要功能

    empty ():判空栈是否为空
    size():获取元素个数
    top():获取栈顶元素
    push()(emplace()等同):将元素入栈
    pop():删除栈顶元素
    swap():两个栈相互交换(c++11支持)

    在这里插入图片描述

    我们清楚了deque和栈的成员函数(功能),可以发现stack的功能是否简单,是完全可以用deque来适配出来的。(通俗点就是用deque包装出stack)

    代码很简单:

    template<class T, class Container = deque<T> >//第一个模板参数是数据类型,第二个模板参数是适配容器
    class stack
    {
    public:
    	void push(const T& val)
    	{
    		_cn.push_back(val);//push数据就相当于deque的尾插
    	}
    	void pop()
    	{
    		_cn.pop_back();//相当尾删
    	}
    	const T& top()
    	{
    		return _cn.back();//栈顶元素就是空间的尾部数据
    	}
    	size_t size()
    	{
    		return _cn.size();//栈的元素个数就是deque的元素个数
    	}
    
    	bool empty()
    	{
    		return _cn.empty();//直接套用deque的判空即可
    	}
    private:
        //成员变量是适配容器
    	Container _cn;
    
    };
    	
    
    • 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

    用deque适配出queue

    与栈不同的是队列是先进先出的,而栈是后入先出的,那么这里的push和pop用deque实现的话就得是尾插和头删了。

    还有就是队头元素的访问是调用的front()函数,队尾是调用back()函数。pop是删除队头元素。

    template<class  T,class Container=deque<T>>
    	class queue
    	{
    	public:
    		void push(const T& val)
    		{
    			_cn.push_back(val);
    		}
    		void pop()
    		{
    			_cn.pop_front();//这里得用头删
    		}
    		const T& front()
    		{
    			return _cn.front();
    		}
    		size_t  size()
    		{
    			return _cn.size;
    		}
    		bool empty()
    		{
    			return _cn.empty();
    		}
    	private:
    		Container _cn;
    	};
    
    • 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

    在这里插入图片描述

    用vector适配出优先级队列

    • 优先级队列的底层结构是一个堆,数组式的容器可以很方便的转化为堆,所以优先级队列可以用vector适配出来。
      在这里插入图片描述

    回顾一下堆:

    请添加图片描述

    向上调整建堆

    请添加图片描述

    向下调整建堆

    请添加图片描述

    既然优先级队列的底层结构是堆 那么想要实现一个优先级队列就必须知道如何建立一个堆(大堆和小堆),而上面的向上和向下调整建堆的算法就必须掌握。

    以建立小堆为例:

    //向上调整 
    void AjustUp(Container& cn,int child)
    {
    	//Compare cmp; 这是个对象,对象是一个()的重载函数 用来比较用的仿函数 下面会介绍这种方式
    	int parent = child / 2;
    	while (parent>=0)
    	{
    		if (_cn[child] < _cn[parent])
    		{
    			swap(_cn[child], _cn[parent]);
    			child = parent;
    			parent = child / 2;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    //向下调整
    void AjustDown(Container& cn,size_t parent)
    {
    	//Compare cmp;
    	int child = parent * 2+1;//左孩子
    	while (child < _cn.size())
    	{
    		/if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
    		{
    			child = child + 1;
    		}
    		if (_cn[child] < _cn[parent])
    		{
    			swap(_cn[child], _cn[parent]);
    			parent = child;
    			child = parent * 2 + 1;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    
    
    • 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

    不支持仿函数的优先级队列

    我们知道堆分大堆小堆,假如一组数据我们定义值越大优先级越高,那么就是在堆顶,这很明显就得建立大堆;反之,如果值越小的优先级越高那么就需要建小堆,那么如果不支持仿函数就必须写两种建堆的算法,也就是向上和向下调整算法都得写两个,但是它们仅仅是比较的符号不同,其他代码完全一致,显然这种方式不够好,代码冗余!!!

    支持仿函数的优先级队列

    当优先级支持仿函数后就不会有上面的代码冗余的问题了,我们可以通过显式的传仿函数给优先级队列,让其内部数据的比较按照我们自定义的仿函数的比较方式来比较,相反的比较方式,得到的堆的类型就是不同的,也就是说我们可以通过控制仿函数的类型实现优先级队列底层建的堆是大堆还是小堆。

    仿函数的结构
    //两种常见的仿函数,根据值的大小比较
    template<class T>
    struct LessCompare
    {
    	bool operator()(T& a,T& b)const //里面是一个()的重载函数
    	{
    		return a < b;
    	}
    };
    
    template<class T>
    struct GreaterCompare
    {
    	bool operator()(T& a, T& b)const
    	{
    		return a > b;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    使用方法和普通方法的比较
    void test()
    {
    	int a=10;
    	int b=29;
    	//普通比较
        cout<<a>b<<endl;
        //仿函数比较
        LessCompare cmp;
        cout<< cmp(a,b) <<endl;//调用()的重载函数进行比较
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    支持仿函数的调整堆算法
    //向上调整
    void AjustUp(Container& cn,int child)
    {
    	Compare cmp;
    	int parent = child / 2;
    	while (parent>=0)
    	{
    		//if (_cn[child] < _cn[parent])
    		if (cmp(_cn[child], _cn[parent]))//将传统的符号比较换成仿函数比较
    		{
    			swap(_cn[child], _cn[parent]);
    			child = parent;
    			parent = child / 2;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    //向下调整
    void AjustDown(Container& cn,size_t parent)
    {
    	Compare cmp;
    	int child = parent * 2+1;//左孩子
    	while (child < _cn.size())
    	{
    		//if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
    		if (child + 1 < _cn.size() && cmp(_cn[child + 1] , _cn[child]))
    		{
    			child = child + 1;
    		}
    		//if (_cn[child] < _cn[parent])
    		if (cmp(_cn[child], _cn[parent]))
    		{
    			swap(_cn[child], _cn[parent]);
    			parent = child;
    			child = parent * 2 + 1;
    		}
    		else
    		{
    			break;
    		}
    	}
    
    }
    
    • 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

    掌握了上面两个调整堆算法,那么优先级队列的实现就已经完成了80%了!!!

    成员函数

    在这里插入图片描述

    结构:
    template<class T,class Container=vector<T>,class Compare=LessCompare<T>>
    class priority_queue
    {
    public:
    	void AjustDown(Container& cn,size_t parent);
    	void AjustUp(Container& cn,int child);
    	void push(const T& val);
    	void pop();
    	const T& top();
    	size_t size();
    	bool empty();
    private:
    	Container _cn;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    模板的分析:
    template<class T,class Container=vector<T>,class Compare=LessCompare<T>>
    
    
    • 1
    • 2

    这里的模板有三个模板参数

    • 第一个参数很好理解就是存储的数据类型
    • 第二个模板参数就是依赖的容器类型这里给的缺省类型是vector
    • 第三个模板参数就是一个类(仿函数),用来方便比较数据,比较的地方就直接用该类里的()重载函数即可,该函数可以自定义,表示比较的方式可以由我们自定义,更加灵活可控。

    成员函数的分析:

    • 结构里的前两个成员函数(调整算法前面已贴出)

    • push()函数就是往vector里尾插数据,然后调用向上调整算法维护堆的结构即可。

    • pop()函数就是将vector的首尾元素交换,将要删除的堆顶元素换到vector的尾部,再调用一次向下调整算法就可以维护堆的结构,最后再将vector的size- - 即可完成堆顶元素的删除。

    • top()函数就是返回堆顶元素,也就是返回vector的首元素了。

    • size()和empty()就直接调用vector的size()和empty()就行了。

    整体代码
    template<class T,class Container=vector<T>,class Compare=LessCompare<T>>
    	class priority_queue
    	{
    	public:
    		void AjustDown(Container& cn,size_t parent)
    		{
    			Compare cmp;
    			int child = parent * 2+1;//左孩子
    			while (child < _cn.size())
    			{
    				//if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
    				if (child + 1 < _cn.size() && cmp(_cn[child + 1] , _cn[child]))
    				{
    					child = child + 1;
    				}
    				//if (_cn[child] < _cn[parent])
    				if (cmp(_cn[child], _cn[parent]))
    				{
    					swap(_cn[child], _cn[parent]);
    					parent = child;
    					child = parent * 2 + 1;
    				}
    				else
    				{
    					break;
    				}
    			}
    
    		}
    		 
    		void AjustUp(Container& cn,int child)
    		{
    			Compare cmp;
    			int parent = child / 2;
    			while (parent>=0)
    			{
    				//if (_cn[child] < _cn[parent])
    				if (cmp(_cn[child], _cn[parent]))
    				{
    					swap(_cn[child], _cn[parent]);
    					child = parent;
    					parent = child / 2;
    				}
    				else
    				{
    					break;
    				}
    			}
    		}
    		void push(const T& val)
    		{
    			_cn.push_back(val);
    			AjustUp(_cn,_cn.size()-1);
    
    		}
    		void pop()
    		{
    			swap(_cn.front(), _cn.back());
    			_cn.pop_back();
    			AjustDown(_cn,0);
    		}
    
    		const T& top()
    		{
    
    			return _cn.front();
    		}
    
    		size_t size()
    		{
    			return _cn.size();
    		}
    
    		bool empty()
    		{
    			return _cn.empty();
    		}
    		
    
    	private:
    		Container _cn;
    	};
    
    • 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
  • 相关阅读:
    偏差数据比对
    HCL Notes/Domino多瑙河第五个Beta版本Drop5发布
    万字长文 - Python 日志记录器logging 百科全书 - 高级配置之 日志分层
    django+xadmin 在线教育网站(一)
    VideoMAE复现之SSV2数据集解压(Linux)、webm转mp4格式
    table的展开折叠按钮操作
    学习-Java输入输出之对象IO流之序列化一个对象
    技术分享 | app自动化测试(Android)--App 控件定位
    计算机毕业设计HTML+CSS+JavaScript仿大型购物商城(1页)
    分类接口,淘宝分类详情 API
  • 原文地址:https://blog.csdn.net/xbhinsterest11/article/details/126084758