深入刨析C++ stl::stack 、stl::queue 及 priority_queue

发布于:2025-03-17 ⋅ 阅读:(10) ⋅ 点赞:(0)

                        欢迎来到干货小仓库!!!

“Stack Overflow 教会我: 提问不可耻,放弃搜索才是”


1.stack的介绍和使用

1.1stack的介绍

1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

遵循 “先进后出” 原则。

                                ​​​​​​​        

1.2stack的使用

函数说明 接口说明
stack() 构造空的栈
empty() 检测stack是否为空
size() 返回stack中元素的个数
top() 返回栈顶元素
push() 将元素 val 压入stack中
pop() 将 stack中 尾部的元素弹出

2.queue的介绍和使用

2.1queue的介绍

1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
遵循"先进先出"原则。

2.2queue的使用

函数声明 接口说明
queue() 构造空的队列
empty() 检测队列是否为空,是返回true,否则返回false
size() 返回队列中的元素个数
front() 返回队头元素的引用
back() 返回队尾元素的引用
push() 在队尾将元素val入队列
pop() 在队头元素出队列

3.priority_queue的介绍和使用

3.1介绍

1.底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
top():返回容器中第一个元素的引用
push():在容器插入元素
pop():删除容器元素
2.标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

3.2使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构。

因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。

函数声明 接口说明
priority_queue() 构造一个空的优先级队列
priority_queue(InputIterator first,InputIterator last) 迭代器初始化
empty() 检测优先级队列是否为空,是返回true,否则返回false
top()
返回优先级队列中最大(最小元素),即堆顶元素
push(const T& val) 在优先级队列中插入val元素
pop()
删除优先级队列中最大(最小)元素,即堆顶元素

4.容器适配器

通过字面理解,所谓的 适配器  就相当于我们在给我们手机充电的时候,电压是220V,难道往我们手机直接充电就是 220W的电压,很显然这不符合我们所理解的常识,我们的充电器的插头就充当了这个角色("适配器").

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

template<class T,class Containter = deque<T>>
class stack
{

    //......

    private:
     Containter _con;   
};

直观的理解就是 一个模板参数,这个模板参数是一个容器(可以是vector、list.....),作为成员变量的类型,根据使用者传的类型,作为成员变量的类型。

为什么stack和queue在STL中并没有将其划分在容器的队列,而是将其称为 "容器适配器"?

这是因为stack 和 queue 只是对其他容器的接口进行了包装。STL中stack 和queue的 默认适配器使用的是 deque。(下面有解析).

5.双端队列(deque)

5.1deque的原理介绍

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

注意:deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。
底层原理:
优缺点:
deque缺陷:
①中间插入和删除.
②频繁的访问(operator[]).
双端队列的迭代器底层实现

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

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

结合了deque的优点,而完美的避开了其缺陷。

6.Stack的模拟实现

template<class T, class Container = deque<T>>
class stack
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}

	void pop()
	{
		_con.pop_back();
	}

	T& top()
	{
		return _con.back();
	}
	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	Container _con;

};

7.queue的模拟实现

template<class T, class Container = deque<T>>
class queue
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}

	void pop()
	{
		_con.pop_front();	//禁止vector当适配器,代价太大
		//_con.erase(_con.begin());
	}

	T& front()
	{
		return  _con.front();
	}

	T& back()
	{
		return _con.back();
	}
	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	Container _con;

};

8.仿函数

仿函数就是一个模板参数,根据这个模板参数可以实现控制数据的排序是按升序还是降序。

这个模板参数封装成了类,重载了operator(),通过调用类模板参数的类的运算符重载(operator > 和operator <)来实现自定义类型的比较。

示例:

特殊情况:仿函数需要自己实现

例如:

9.priority_queue模拟实现

template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
	Compare com;
	//向下调整
	void AdjustDown(int parent)
	{
		int child = parent * 2 + 1;

		while (child < _con.size())
		{
			//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
			if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				++child;

			if (com(_con[parent], _con[child]))
			{
				swap(_con[child], _con[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
				break;
		}
	}
	//向上调整
	void AdjustUp(int child)
	{
		Compare com;
		int parent = (child - 1) / 2;

		while (child > 0)
		{
			//if (_con[child] >_con[parent])
			if (com(_con[parent], _con[child]))
			{
				swap(_con[child], _con[parent]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
				break;
		}
	}
public:

	priority_queue()
	{
	}

	//迭代器初始化
	template<class Inputiterator>
	priority_queue(Inputiterator first, Inputiterator last)
	{
		while (first != last)
		{
			_con.push_back(*first);
			++first;
		}

		//建堆
		for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
		{
			AdjustDown(i);
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		//向上调整
		AdjustUp(_con.size() - 1);

	}

	void pop()
	{
		swap(_con[0], _con[_con.size() - 1]);
		_con.pop_back();

		AdjustDown(0);
	}
	const T& top()
	{
		return _con[0];
	}
	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	Container _con;
};


觉得不错的,记得点赞+收藏!!!