C++的STL---->stack & queue &priority_queue


stack的简介以及使用
stack就是我们先前在数据结构里面使用过的栈这个数据结构,它仅仅允许在一段插入和删除数据,这一段被称作为栈顶。 同样,我们也通过官方文档来认识一下stack有什么接口可以使用:
在这里插入图片描述
接下来我们就通过一段简单的代码来看一看怎么使用stack

#include<iostream>
#include<stack>
using namespace std;
void test()
{
	std::stack<int>st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	while (!st.empty())
	{
		int top = st.top();
		cout << top << " ";
		st.pop();
	}
	cout << endl;
}
int main()
{  
   test();
  return 0;
}

在这里插入图片描述
关于stack的功能介绍就到这里,以后需要使用数据结构里面的stack我们就无需自己写了。


queue的简介和使用
queue是我们数据结构里面的队列,遵循先进先出的原则。 我们同样是通过官方文档来看一看queue的使用。
在这里插入图片描述
和stack不一样的是,queu可以取队头的数据的同时也可以取队尾的数据,但是我们都是取队头的数据。

#include<iostream>
#include<queue>
using namespace std;
void test()
{
	queue<int>q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	while (!q.empty())
	{
		int font = q.front();
		cout << font << " ";
		q.pop();
	}
	cout << endl;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
同样对于queue的使用我们就介绍到这里。


priority_queue的使用和介绍
实际上,除了普通的栈和队列以外。官方库还提供了优先级队列,优先级队列的行为就是数据结构里面的堆。下面我们来看一看优先级队列priority_queue怎么使用。一样,我们还是通过官方文档来看。
在这里插入图片描述
接下来我们也透过代码来看一看priority_queue怎么使用

#include<iostream>
#include<queue> //priority_queue也在这个文件
using namespace std;
void test()
{
	priority_queue<int>pq;
	pq.push(4);
	pq.push(6);
	pq.push(2);
	pq.push(8);
	while (!pq.empty())
	{
		int top = pq.top();
		cout << top << " ";
		pq.pop();
	}
	cout << endl;
}
int main()
{   
	test();
	return 0;
}

在这里插入图片描述
这里默认形成的是大堆,而如果要生成一个小堆,那么就要传递仿函数。

#include<iostream>
#include<queue>
#include<functional> //官方库定义的仿函数所在的头文件
void test()
{ 
   //这里的greator是一个仿函数
	priority_queue<int,vector<int>,greater<int>>pq;
	pq.push(4);
	pq.push(6);
	pq.push(2);
	pq.push(8);
	while (!pq.empty())
	{
		int top = pq.top();
		cout << top << " ";
		pq.pop();
	}
	cout << endl;
}
int main()
{   
	test();
	return 0;
}

在这里插入图片描述
关于priority_queue的使用就介绍到这里,下面和我一起认识一下仿函数。


仿函数介绍
我们介绍一下什么是仿函数。首先我们回顾C语言的qsort,我们知道qsort用了第四个参数是函数指针。因为函数指针的存在,所以qsort可以根据函数指针指定的排序规则排序,但是函数指针的结构太复杂了!而且不符合面向对象程序的设计! 为了能够做到指定规则排序,所以C++设计了仿函数来代替函数指针的作用,而仿函数的本质就是一个operator()的类。

//设计一个仿函数。
//也可以用class,不过operator()就要声明成public的函数
template<class T>
struct Less
{
   //重载()
   bool operator()(const T& a,const T& b)const
   { 
        return a<b;
   }
};

官方库里面写好了less和greater两个仿函数,它们定义在functional头文件里面。
有的时候,我们需要自己定义仿函数,不过大多数场景下,官方库里面定义的这两个仿函数已经就够用了。


什么是适配器模式
仔细观察官方库里面的stack和queue的声明

template <class T, class Container = deque<T> > class stack;
template <class T, class Container = deque<T> > class queue;

我们看到,这里给了一个模板参数Container 其实严格意义上来说,stack和queue并不是容器,而是容器适配器。 既然是容器适配器,就要提到很重要的设计思想---->适配器模式。
所谓的适配器模式,就是借助类似"充电头"这样的转换器来帮助转换出我们想要的,而这里的充电头就是模板参数Container!以stack为例,如果这里的Container是vector,那么我们的栈就是数组栈,而如果是list,那么生成的就是链式栈! 借助适配器,我们无需关心stack使用什么数据结构实现。同理queue也是一样!


stack的模拟实现
接下来我们来模拟实现一下stack

#pragma once
#include <iostream>
#include<deque>
#include<vector>
#include<list>
using namespace std;
namespace chy
{
   //stack是容器适配器,不独立实现,而是借用其他容器生成
	template<typename T,typename 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();
		}
		const T& top()const
		{
			return _con.back();
		}
		bool empty() const
		{
			return _con.empty();
		}
		size_t size() const
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}
void test_st()
{
	chy::stack<int>st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	cout << "namespace chy" << endl;
	while (!st.empty())
	{
		int top = st.top();
		cout << top << " ";
		st.pop();
	}
	cout << endl;

}
int main()
{   test_st();
   return 0;
}

在这里插入图片描述

这里的默认使用的是deque进行适配,deque是一个可以在头尾快速插入删除的双端队列,并且能够支持随机访问,但是其实是一个"外强中干"的数据结构,最后我们会介绍一下这个容器。


queue的模拟实现
接下来我们来模拟实现以下queue

#pragma once
#include<iostream>
#include<list>
#include<deque>
using namespace std;
namespace chy
{
	template<typename T,typename Container=deque<T>>
	class queue
	{ 
		//使用其他容器适配生成,不可以使用vector适配
		//因为vector没有pop_front()成员
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			_con.pop_front();
		}
		bool empty() const
		{
			return _con.empty();
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		const T& front()const
		{
			return _con.front();
		}
		const T& back() const
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}
void test_queue1()
{
	//使用list适配,不能用vector,因为vector没有pop_front()
	chy::queue<int, list<int>>q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	cout << "namespace chy" << endl;
	while (!q.empty())
	{
		int top = q.front();
		cout << top << " ";
		q.pop();
	}
	cout << endl;
}
int main()
{  
   test_queue1();
   return 0;
}

在这里插入图片描述


priority_queue的模拟实现
最后就是优先级队列的模拟,相比于前面两个直接适配。优先级队列push要向上调整,而pop()的时候要向下调整,所以相对会复杂一点。

#pragma once
#include<iostream>
#include<vector>
#include<functional>
#include<queue>
using namespace std;
namespace chy
{   
	//compare是一个仿函数
    template<class T,class Container=vector<T>,class Compare=less<T>>
	class priority_queue
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
			_adjustup(_con.size() - 1);
		}
		void pop()
		{   
			//删除交换堆顶,然后删除
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			_adjustdown(0);
		}
		const T& top()const
		{
			return _con[0];
		}
		bool empty()const
		{
			return _con.empty();
		}
		size_t size()const
		{
			return _con.size();
		}
    //堆需要调整,所以我们要提供调整算法
	private:
		void _adjustup(size_t child)
		{
			Compare func;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (func(_con[parent],_con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				//其他位置都处理好了
				else
				{
					break;
				}
			}
		}
		void _adjustdown(size_t parent)
		{   
			Compare func;
			size_t child = 2 * parent + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && func(_con[child],_con[child+1]))
				{
					++child;
				}
				if (func(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}
	private:
		Container _con;
	};
}
void test_pq()
{
	chy::priority_queue<int> pq;
	pq.push(4);
	pq.push(6);
	pq.push(2);
	pq.push(8);
	cout << "namespace chy" << endl;
	while (!pq.empty())
	{
		int top = pq.top();
		cout << top << " ";
		pq.pop();
	}
	cout << endl;
}
void test_pq1()
{
	chy::priority_queue<int, vector<int>, greater<int>> pq;
	pq.push(4);
	pq.push(6);
	pq.push(2);
	pq.push(8);
	cout << "namespace chy" << endl;
	while (!pq.empty())
	{
		int top = pq.top();
		cout << top << " ";
		pq.pop();
	}

	cout << endl;
}
int main()
{   
	test_pq();
	test_pq1();
	//test();
	return 0;
}

在这里插入图片描述


外强中干的deque
可能细心的你已经注意到了,stack和queue的默认适配容器都是deque,那么这个deque究竟是何许神仙呢?其实这个deque并没有什么特别神奇的地方,它的结构非常复杂,下面是一个deque的结构图
在这里插入图片描述
其实,deque本质上划分了一块一块很小的内存,接着使用了1个指针数组来记录每一个小内存块的地址(为了支持随机访问)。但其实真实随机访问的效率比vector慢非常多!所以看似有着vector和list的所有优点,但是实际上并不是特别实用。但是deque的优点特别适合stack和queue,所以stack和queue都是默认使用deque来适配


以上就是本篇文章的全部内容,如果有不足或错误之处还望指出。希望大家共同进步。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值