【STL】| C++ 栈和队列(详解、deque(双端队列)介绍、容器适配器的初步引入)

目录

前言

总代码

容器适配器的引入

栈 stack

队列 queue

栈和队列用法简介

队列

deque简介(了解即可)

结语


前言

今天我们要讲解的结构是栈和队列

这两个的具体实现相比于前面我们学的string、vector、list都要简单得多(因为容器适配器的引入)

如果有需要了解这两个数据结构的具体函数用法的话,也可以在较为官方的网站上进行查阅,如下

https://legacy.cplusplus.com/reference/stack/stack/?kw=stack

总代码

如果有友友只是复习需要,只想看完整代码的话,可以直接点下面的gitee链接

当然对于看完了整篇文章的友友也可以照着这里的代码敲一篇进一步理解喔...(* ̄0 ̄)ノ

gitee - stack & queue - blog - 2024-08-08

容器适配器的引入

在之前,我们实现的数据结构都是使用的迭代器,但是今天这两个并不是使用迭代器的,因为栈和队列这两个就是插入数据,拿出数据,出栈、出队列,并没有遍历一遍我看看里面的数据一说

所以我们今天会用到一个容器适配器

什么是容器适配器呢?

现象上来看就是一个函数模板上面的参数,我们将已经存在的数据结构如vector、list、deque等作为模板供类使用

就拿栈来举例子吧,试想一下,我们的栈就是一个入栈、出栈

而这两个动作在我们的vector里就是尾插、尾删

同样的,什么empty、size、top(就是vector里面的back),都能使用这些函数的功能,所以我们就不需要自己写,我们只需要使用这些已存在的容器即可

举个例子:

template<class T, class container = vector<T>>

而我们的类,内部的成员就可以是 container _con;

栈 stack

如上所述,我们直接上代码:

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

namespace hjx
{
	template<class T, class container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

		const T& top()
		{
			return _con.back();
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		container _con;
	};
}

我们可以看到,全程都是使用我们容器适配器的功能在复用过来实现我们的栈

这里有一点值得一提的是,我们的栈不仅仅可以用vector作为适配器,用list或者一个叫做deque的容器也可以,因为只要有尾插尾删(高效的前提下),就能被用来实现栈

队列 queue

先上代码:

#include<iostream>
using namespace std;
#include<list>

namespace hjx
{
	template<class T, class container = list<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();
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		container _con;
	};
}

我们可以看到,list和stack大部分地方都是相似的,唯一不同的两点就在于:

  1. 适配器容器stack可以使用vector,但是队列不行,因为vector的头删要挪动数据效率低
  2. queue没有top,而是front和back

栈和队列用法简介

栈符合的原理是后进先出,图示如下:

栈最常用的就是入栈、出栈,如果要将全部数据取出来的话,就写一个while循环判断栈是否为空(empty),然后挨个取  top(),出栈

代码如下(仅展示最常用的):

void test_stack1()
{
	stack<int> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	while (!s1.empty())
	{
		cout << s1.top() << " ";
		s1.pop();
	}
}

队列

队列和栈同理,就是入队列,出队列

只不过和栈不同的点在于,栈是后进先出,队列是先进先出,图示如下:

而如果要将数据全部取出来的话,我们同样需要写一个while循环判断是否为空,然后取对头,出队列

代码如下(仅展示最常用的):

void test_queue1()
{
	queue<int> q1;
	q1.push(1);
	q1.push(2);
	q1.push(3);
	q1.push(4);
	q1.push(5);

	while (!q1.empty())
	{
		cout << q1.front() << " ";
		q1.pop();
	}
}

deque(双端队列)简介(了解即可)

其实,C++标准库里面,栈和队列这两个的默认容器适配器是deque(模板缺省参数那里)

template<class T, class container = deque<T>>

这就要谈到一段历史了,C++曾想要打造一个容器,让其既像vector也像list

这个容器就是deque

但其实这个数据结构有点像骡子(马和驴的结合体)

马能跑得很快,但是不耐造。驴能背很重的货物,但是脾气很倔

骡子怎么说呢,没有马跑得快,但比驴快一点。没有驴那么能背重,但比马好一点

综上我们可以看到,deque现在就处于这么一种尴尬的处境

上图所示是vector和list

vector能使用operator[]快速访问数据,尾插尾删效率也高,但是头插头删要挪动数据效率极低

list头插头删、尾插尾删效率都很高,但是不能直接使用operator[]访问,sort的效率远低于vector

这个数据结构可以理解为是一个二维数组(不能完全这么看,但大致可以借助这个去理解deque)

试想,如果我们要头插头删的话,我们只需要对最后面的那个小数组动手脚,就像vector一样的尾删

如果我们要头插头删的话,我们就在前面多加一个buff小数组,在多加的那个小数组的尾部插入删除数据即可,图示如下:

目前看来,这个数据结构还是可以的,但是我们接下来来讲一讲他的缺点

首先,deque是支持operator[]的

但是如果我们在中间插入数据的话,那么这里只有两种处理情况

  1. 要插入数据的那个小数组扩容,但是这样的话,我们每个小数组的大小就不是都是10了这样的话我们就不能直接使用÷和%快速找到,也就是operator[]的效率就会变得很低,我们得一个数组一个数组地加(大小),几个还好,如果我有一万个小数组呢?
  2. 直接挪动数据,但是这样的话,就和vector一样了,效率会变得很低,还不如使用list

综上我们可以看到,deque最好的使用场景就是只有头插头删,尾插尾删、且有一点operator[]的情况

但是deque终究不能代替vector和list,所以我们了解就好

结语

这篇博客到这里,对栈和队列的讲解就结束啦(~ ̄▽ ̄)~

如果觉得对你有帮助的话,请务必多多支持博主喔<(_ _)>~( ̄▽ ̄)~*

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值