C++初阶 — stack和queue

目录

一、stack的介绍和使用

 1. stack的介绍

 2. stack的使用

  3. stack的模拟实现

二、queue的介绍和使用

 1. queue的介绍

 2. queue的使用

 3. queue的模拟实现

三、容器适配器

 1. 什么是适配器

 2. STL标准库中stack和queue的底层结构

 3. deque的简单介绍

   3.1 deque的原理介绍

    3.2 deque的缺陷

   3.3 为什么选择deque作为stack和queue的底层默认容器

四、 priority_queue的介绍和使用

 1. priority_queue的介绍(堆)

 2. priority_queue的使用

 3. priority_queue的模拟实现

 反向迭代器


一、stack的介绍和使用

 1. stack的介绍

  • stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
  • stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
  • stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

                empty:判空操作

                back:获取尾部元素操作

                push_back:尾部插入元素操作

                pop_back:尾部删除元素操作

  • 标准容器vectordequelist均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque

 2. stack的使用

  3. stack的模拟实现

        这里直接写一个容器适配器,复用一下。默认stack的存储空间的类型是vector,或者自己传也可以,只要结构能符合栈的实现就可以。这里库的默认容器是deque(双端队列)后面一点讲。

namespace bite
{
    //template<class T, class Container = deque<T>>
    template<class T, class Container = vector<T>>
	class stack
	{
	public:
		stack()
		{}
        //插入
		void push(const T& x)
		{
			_c.push_back(x);
		}
        //删除
		void pop()
		{
			_c.pop_back();
		}
        //获取栈顶元素
		T& top()
		{
			return _c.back();
		}
    
		const T& top()const
		{
			return _c.back();
		}
        //数据个数
		size_t size()const
		{
			return _c.size();
		}
        //是否为空
		bool empty()const
		{
			return _c.empty();
		}
	private:
		Container _c;
	};

}
操作演示:

二、queue的介绍和使用

 1. queue的介绍

  • 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
  • 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
  • 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

                empty:检测队列是否为空

                size:返回队列中有效元素的个数

                front:返回队头元素的引用

                back:返回队尾元素的引用

                push_back:在队列尾部入队列

                pop_front:在队列头部出队列

  • 标准容器类dequelist满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque

 2. queue的使用

 3. queue的模拟实现

        队列这里跟栈差不多,只不过默认容器要改成list,用vector会报错。因为vector头删效率不高,在vs上用会报错。

namespace byte
{
    //template<class T, class Container = deque<T>>
	template <class T, class Container = list<T>>
	class queue
	{
	public:
		queue()
		{}
		void push(const T& x)//插入
		{
			_c.push_back(x);
		}
		void pop()//删除
		{
			_c.pop_front();
		}
		T& back()//队尾数据
		{
			return _c.back();
		}
		const T& back()const
		{
			return _c.back();
		}
		T& front()//队头数据
		{
			return _c.front();
		}
		const T& front()const
		{
			return _c.front();
		}
		size_t size()const//元素个数
		{
			return _c.size();
		}
		bool empty()const//是否为空
		{
			return _c.empty();
		}
	private:
		Container _c;
	};
}

操作演示:

三、容器适配器

 1. 什么是适配器

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

 2. STL标准库中stackqueue的底层结构

        虽然stack queue 中也可以存放元素,但在 STL 中并没有将其划分在容器的行列,而是将其称为 容器适配 ,这是因为 stack 和队列只是对其他容器的接口进行了包装, STL stack queue 默认使用 deque。

 3. deque的简单介绍

   3.1 deque的原理介绍

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

        虽然它叫双端队列,但是它并不符合先进先出,从功能上来说它兼具了vector和list的优点,但是它在实际中用的不多。它虽然集优点于一身,但是优点却不像vector和list那样的极致,只能说有用但不是特别有用。如果有什么问题或者不会使用的,可以上网站上看一看文档:Reference - C++ Reference (cplusplus.com)

        deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维 数组 ,其底层结构如下图所示:

        双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“ 整体连续 以及随机访问的假象,落 在了 deque 的迭代器身上, 因此 deque 的迭代器设计就比较复杂,如下图所示:

    3.2 deque的缺陷

  • vector比较deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
  • list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
  • 但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vectorlistdeque的应用并不多,而目前能看到的一个应用就是,STL用其作stackqueue的底层数据结构

   3.3 为什么选择deque作为stackqueue的底层默认容器

  1. stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
  2. stackqueue不需要遍历(因此stackqueue没有迭代器),只需要在固定的一端或者两端进行操作。
  3. stack中元素增长时,dequevector的效率高(扩容时不需要搬移大量数据)queue中的元素增长时,deque不仅效率高,而且内存使用率高。
  4. 结合了deque的优点,而完美的避开了其缺陷。

四、 priority_queue的介绍和使用

 1. priority_queue的介绍(堆)

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 ( 优先队列中位于顶部的元素)
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从特定容器的“ 尾部 弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
        empty():检测容器是否为空
        size():返回容器中有效元素个数
        front():返回容器中第一个元素的引用
        push_back():在容器尾部插入元素

        pop_back():删除容器尾部元素
5. 标准容器类 vector deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用vector
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、 push_heap pop_heap 来自动完成此操作。

 2. priority_queue的使用

        优先级队列默认使用vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中元素构造成 堆的结构,因此 priority_queue 就是堆,所有需要用到堆的位置,都可以考虑使用 priority_queue 。注意: 默认情况下 priority_queue 是大堆

 3. priority_queue的模拟实现

         优先级队列其实就是数据结构的堆,默认容器是vector,添加了仿函数。这里默认是建大堆(less),也可自己传小堆建堆(greater)。其实就是重载了一个()。

#include <iostream>
using namespace std;
#include <vector>
#include <functional>

namespace byte
{
	// 仿函数/函数对象  //可以用库里面的
	/*template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const  T& y)const
		{
			return x < y;
		}
	};
	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)const
		{
			return x > y;
		}
	};*/
	template<class T, class Container = vector<int>,class Compare = less<T>>
	class priority_queue
	{
	public:
		//如果没有迭代器构造可以不写,编译器会默认生成,有迭代器构造就得写一个放这,让编译器走初始化列表
		priority_queue()
		{}
		//用迭代器构造建堆
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:c(first,last)
		{
			for (size_t i = (c.size() - 1 - 1) / 2; i >= 0; --i)
			{
				AdJustDown(i);
			}
		}
		//向上调整
		void AdJustUp(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (c[parent] < c[child])
				if (com(c[parent] , c[child]))
				{
					swap(c[parent], c[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}
		void push(const T& x)
		{
			c.push_back(x);
			AdJustUp(c.size() - 1);
		}
		//向下调整
		void AdJustDown(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < c.size())
			{
				//if ( child+1 < c.size() && c[child] > c[child + 1])
				if (child + 1 < c.size() && com(c[child] , c[child + 1]))
					child++;
				//if (c[parent] < c[child])
				if (com(c[parent] , c[child]))
				{
					swap(c[parent], c[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
					break;
			}
		}
		void pop()
		{
			swap(c[0], c[c.size() - 1]);
			c.pop_back();
			AdJustDown(0);
		}
		const T& top()const
		{
			return c[0];
		}
		size_t size()const
		{
			return c.size();
		}
		bool empty()const
		{
			return c.empty();
		}
	private:
		Container c;
	};
};

操作演示:

反向迭代器

// 给不同容器的正向迭代器,适配出对应的这个容器需要的反向迭代器
template<class iterator,class Ref,class Ptr>
class ReveserIterator
{
	typedef ReveserIterator <iterator,Ref,Ptr> Self;
public:
	ReveserIterator(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)const
	{
		return _it != s._it;
	}
	bool operator==(const Self& s)const
	{
		return _it == s._it;
	}
private:
	iterator _it;
};

猜你喜欢

转载自blog.csdn.net/weixin_68993573/article/details/128669467
今日推荐