【C++】stack和queue的使用和实现(附加deque的简单介绍)

文章目录

  • [1. 前言](#1. 前言)
  • [2. stack的介绍](#2. stack的介绍)
  • [3. stack的实现](#3. stack的实现)
    • [1. 铺垫](#1. 铺垫)
    • [2. push](#2. push)
    • [3. pop](#3. pop)
    • [4. top](#4. top)
    • [5. size](#5. size)
    • [6. empty](#6. empty)
    • [7. 测试](#7. 测试)
  • [4. queue的介绍](#4. queue的介绍)
  • [5. queue的实现](#5. queue的实现)
    • [1. 铺垫](#1. 铺垫)
    • [2. push](#2. push)
    • [3. pop](#3. pop)
    • [4. front](#4. front)
    • [5. back](#5. back)
    • [6. size](#6. size)
    • [7. empty](#7. empty)
    • [8. 测试](#8. 测试)
  • [6. deque的简单介绍](#6. deque的简单介绍)
    • [1. 为什么不能取代vector](#1. 为什么不能取代vector)
    • [2. 为什么不能取代list](#2. 为什么不能取代list)
    • [3. 对于stack和queue而言](#3. 对于stack和queue而言)
  • [7. stack和queue的源代码](#7. stack和queue的源代码)
    • [1. stack.h](#1. stack.h)
    • [2. queue.h](#2. queue.h)
    • [3. test.cpp](#3. test.cpp)

1. 前言

在前面我们已经学习了string、vector、list等stl容器的使用以及实现,今天我们来看一下stack和queue,其实就是对应我们的栈和队列------>>>点击查看栈和队列

2. stack的介绍

  1. stack是一种容器配置器,什么是容器配置器呢?我们在后续实现的时候就能深切感受到了,我们这里粗略形容一下就是stack是可以用其他的容器来实现的,比如vector、list,这里底层实现默认是采用deque,我们后续会简单介绍deque
  2. stack就是我们数据结构中的栈,先进后出,只能从容器的一端进行数据的插入和删除操作
  3. 适配stack的容器支持以下接口:(push_back,尾部插入数据),(pop_back,尾部删除数据),(back,获取尾部数据的引用),(size,获取元素的个数),(empty,判空)

3. stack的实现

1. 铺垫

  1. 我们依然是采用模板来设计的,所以我们创建两个文件分别为stack.h和test.cpp分别用于实现基本功能和测试
  2. 我们前面说到,stack是一种容器适配器,所以它的模板参数是传一个容器,默认是deque,为了方便大家理解,我们这里先默认使用vector。
cpp 复制代码
#pragma once

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

template<class T, class Container = vector<T>>
class stack
{
public:
private:
	Container _con;
};

2. push

cpp 复制代码
void push(const T& x)
{
	_con.push_back(x);
}
  1. 我们的push是为了尾插一个数据,实现是不是超级简单,这里我们直接使用我们传入的容器类型的push_back就可以实现我们的push
  2. 针对这里的T类型,我们不进行修改所以使用const修饰,由于T可以是任意容器类型,所以我们这里直接传T的引用可以减少传参的消耗
  3. 如果我们传入的容器不支持push_back呢?相信大家会有这样的烦恼,这是因为大家接触容器配置器比较少,比较陌生,这样给大家举个例子,我们使用list会用下标访问吗?肯定不会啊,为什么?因为会报错会不支持啊,所以如果我们这里传入的容器不支持push_back的话就会直接报错,也不需要加多余的判断,我们传入的容器自己就报错了

3. pop

cpp 复制代码
void pop()
{
	_con.pop_back();
}
  1. 我们的pop是为了尾删一个数据,实现起来也是十分简单,我们直接使用传入的容器自己的pop_back就可以了
  2. 这里针对如果传入的容器不支持pop_back的问题我们就不再赘述了,原因跟在push那里是一样的

4. top

cpp 复制代码
T& top()
{
	return _con.back();
}
  1. top是为了返回栈顶数据的引用,我们直接调用对应容器的back接口就可以返回末尾数据的引用了
  2. 为了返回栈顶数据的引用所以我们这里返回值类型是T&

5. size

cpp 复制代码
size_t size() const
{
	return _con.size();
}
  1. size是为了获取元素的个数,所以我们直接调用传入容器的size接口就可以了
  2. 由于容器的大小不可能为负数,所以我们这里使用的是size_t类型
  3. 我们是这里针对size是不可能允许做任何修改的,所以我们这里直接使用const修饰this指针就可以了

6. empty

cpp 复制代码
bool empty() const
{
	return _con.empty();
}
  1. empty就是来判空的,我们只需要调用对应容器的empty接口就可以了
  2. 这里我们也不做任何修改所以直接用const修饰this指针就可以

7. 测试

cpp 复制代码
#include "stack.h"
#include<stack>

int main()
{
	William::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	cout << st.size() << endl;
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	return 0;
}

4. queue的介绍

  1. queue也是一种容器配置器,默认也是采用deque来实现
  2. queue就是我们数据结构种的队列,先进先出,元素从队尾入队列,从队头出队列
  3. 适配queue的容器应该支持以下接口:(push_back,尾插),(pop_front,头删),(front,获取头部数据的引用),(back,获取尾部数据的引用),(size,获取数据个数),(empty,判空)

5. queue的实现

1. 铺垫

  1. 我们依然是采用模板来设计的,所以我们创建两个文件分别为queue.h和test.cpp分别用于实现基本功能和测试
  2. 我们前面说到,queue也是一种容器适配器,所以它的模板参数是传一个容器,默认是deque,为了方便大家理解,我们这里先默认使用list。
cpp 复制代码
#pragma once

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

namespace William
{
	template<class T, class Container = list<T>>
	class queue
	{
	public:
	private:
		Container _con;
	};
}

2. push

cpp 复制代码
void push(const T& x)
{
	_con.push_back(x);
}
  1. 我们的push是为了尾插一个数据,这里我们直接使用我们传入的容器类型的push_back就可以实现我们的push
  2. 针对这里的T类型,我们不进行修改所以使用const修饰,由于T可以是任意容器类型,所以我们这里直接传T的引用可以减少传参的消耗

3. pop

cpp 复制代码
void pop()
{
	_con.pop_front();
}
  1. 我们的pop是为了尾删一个数据,我们直接使用传入的容器自己的pop_back就可以了

4. front

cpp 复制代码
T& front()
{
	return _con.front();
}
  1. front是用于获取头部的数据的引用,这里我们调用front即可,其返回的就为头部数据的引用

5. back

cpp 复制代码
T& back()
{
	return _con.back();
}
  1. back是用于获取尾部的数据的引用,这里我们调用back即可,其返回的就为尾部数据的引用

6. size

cpp 复制代码
size_t size() const
{
	return _con.size();
}
  1. size是为了获取元素的个数,所以我们直接调用传入容器的size接口就可以了
  2. 由于容器的大小不可能为负数,所以我们这里使用的是size_t类型
  3. 我们是这里针对size是不可能允许做任何修改的,所以我们这里直接使用const修饰this指针就可以了

7. empty

cpp 复制代码
bool empty() const
{
	return _con.empty();
}
  1. empty就是来判空的,我们只需要调用对应容器的empty接口就可以了
  2. 这里我们也不做任何修改所以直接用const修饰this指针就可以

8. 测试

cpp 复制代码
#include "queue.h"

int main()
{
	William::queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);

	cout << q.front() << " " << q.back() << endl;
	cout << q.size() << endl;

	while(!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
	return 0;
}

6. deque的简单介绍


  1. deque双端队列,是一种双开口的"连续"空间的数据结构,双开口的含义是可以在头尾两端进行数据的插入和删除操作,且时间复杂度为O(1)
  2. deque也可以这样理解,就是list和vector的缝合怪,既可以很好地实现头插头删,也支持下标访问
  3. 它的底层主要是四个指针,分别是cur,first,last,node,它的结构是有一个中控数组,这个中控数组是从中间的位置开始插入一段段buff(从中间开始方便头插),cur指向当前所处的buff,first指向当前buff的起始位置,last指向当前buff的结束位置,node指向当前buff在中控数组所处的位置,我们中控数组是连续的,所以node+1就是下一个buff的地址

1. 为什么不能取代vector

  1. 首先我们要肯定的是它肯定是比vector的头插头删效率高的
  2. 但是它的下标访问就跟vector差的有点多了,在初步了解deque之后我们可以感受到deque的下标访问的计算量还是不小的,deque也是可以使用sort函数的,大家可以去自己测试一下vector的sort和deque的sort差了多少,甚至先把deque拷贝到vector排序后再拷贝回deque的时间都比直接排序deque要快

2. 为什么不能取代list

  1. deque支持下标访问这一点是比list厉害不少的,而且它也不需要频繁地在堆上申请空间,缓存利用率也比list高,头插头删效率也很好
  2. 但是在中间插入数据就垮掉了,deque在中间插入数据也是需要挪动数据的

3. 对于stack和queue而言

  1. 对于stack和queue来说用deque确实很合适
  2. stack和queue是不需要对中间的数据做任何改动的,它俩只涉及头尾数据的改动,这就完美符合stack和queue的要求了

7. stack和queue的源代码

1. stack.h

cpp 复制代码
#pragma once

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

namespace William
{
	template<class T, class Container = vector<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() const
		{
			return _con.size();
		}

		bool empty() const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

2. queue.h

cpp 复制代码
#pragma once

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

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

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

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

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

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

		bool empty() const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

3. test.cpp

cpp 复制代码
#include "stack.h"
#include "queue.h"

int main()
{
	William::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	cout << st.size() << endl;
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}

	William::queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);

	cout << q.front() << " " << q.back() << endl;
	cout << q.size() << endl;

	while(!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
	return 0;
}
相关推荐
山甫aa2 小时前
二叉树遍历----从零开始的数据结构
数据结构·c++·二叉树
hhb_6182 小时前
D架构底层调度与性能优化实践指南
开发语言
秋92 小时前
Java AI编程工具全景解析:功能、收费与工单系统实战指南
java·开发语言·ai编程
会编程的土豆2 小时前
【go】 Go语言中的 defer:从入门到理解底层机制(讲透版)
开发语言·后端·golang
一只幸运猫.2 小时前
Google Mug库——一个现代的通用工具库
开发语言·python
民乐团扒谱机2 小时前
【附完整代码】Python爬取古筝网曲谱图片一键生成PDF(下·PDF生成与GUI篇)
开发语言·python·pdf
代码中介商3 小时前
C语言操作符深度解析:从基础到高级应用
c语言·开发语言
z小天才b3 小时前
Java 设计模式完全指南:从入门到精通
java·开发语言·设计模式
cpp_25013 小时前
P2249 【深基13.例1】查找
数据结构·c++·算法·题解·二分·洛谷