冰冰学习笔记:适配器与仿函数

欢迎各位大佬光临本文章!!!

还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool


系列文章推荐

冰冰学习笔记:《一步一步带你实现《堆与堆排序》》

冰冰学习笔记:《一步一步带你实现《栈与队列》》

冰冰学习笔记:《list的简单模拟》


目录

系列文章推荐

前言

1.容器的适配器

2.deque的了解

3.优先级队列与仿函数


前言

        在学习栈与队列的使用时我们发现,文档中并没有把这两个归纳为容器,而是出现了新的概念,适配器。那什么是适配器呢?

1.容器的适配器

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

        在栈与队列中,我们发现栈与队列这两种数据结构为了保证他们本身的性质,并不会提供迭代器来支持随机访问。拿栈来说,栈是一种先入后出的容器,因此栈的接口函数只有很少的几个。而这些接口中vector与list都是支持的,因此栈完全可以使用vector和list进行封装,而不用自己实现。

        因此我们在实现栈或者队列的时候,只需要增加一个模板参数,并提供默认缺省值,既能让用户可以显示提供适配器来构建栈或者队列,也可以直接使用默认提供的适配器进行构建。

        当用户调用时既可以显示调用,也可以直接调用。

        因此,我们的模拟实现也非常简单,甚至连构造函数都不用写,因为构造时会去调用适配器的默认构造函数。

栈的模拟实现
	template<class T,class Container=deque<T>>
	class stack
	{
	public:
		void push(const	T& x) { _st.push_back(x);}
		void pop() { _st.pop_back();}
		T& top() { return _st.back();}
		const T& top()const { return _st.back();}
		bool empty() { return _st.empty();}
		size_t size()const { return _st.size();}
	private:
		Container _st;
	};

2.deque的了解

        但是栈与队列并没有使用vector或者list作为自己的适配器,尤其是队列,vector本身就不提供头删更不能使用。所以库中这两个容器的适配器都采用了deque。

        deque(双端队列)是一种双开口的"连续"空间的数据结构,它支持在头尾两端进行O(1)的插入和删除,并且还支持随机访问。与vector相比,deque头插效率高,不用频繁挪动数据,与list相比,其内部空间利用率比较高,且支持随机访问。

        deque真的是连续的空间吗?其实不然,deque所谓的连续空间是由一块块连续的小空间拼接而成,我们可以将其理解为list中存储了大量一摸一样的vector,每一段小空间都是连续的,并且大小一样,小空间之间通过指针相连接。 

        而deque为了维护空间的随机访问,使其迭代器非常复杂,其迭代器内部具备4个指针,使用4个指针来进行区间的维护。

        实现原理非常复杂,简单来说就是使用前三个指针维护每一段buff区间,node指针则用来维护buffer之间的联系,当一个buffer已经遍历完毕时,node将向后迭代,指向另一块连续的空间,并且更新cur,first,last指针。

        在进行随机访问时,首先deque先计算出该下表位于第几个buffer,然后去相应的buffer进行寻找。但是频繁的随机访问使得deque的效率并不高。

        deque的优缺点比较明显,与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector高的。 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

        但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

3.优先级队列与仿函数

        优先级队列是队列结构的一种,其底层就是我们之前实现的堆结构。我们使用优先级队列取出元素时,取到的元素必然为堆顶的元素。优先级队列的默认实现是大堆,当我们想实现小堆时就需要使用仿函数来实现。

        那什么是仿函数呢?仿函数并不是函数,而是对()运算符的重载。本质是一个类,使用时需要创建对象进行调用,看起来形如函数调用。

        通过仿函数的使用,我们可以进行泛型编程,使用显示传递的仿函数来控制某些判断条件,从而实现不同的排列方式。

        例如优先级队列中的调整算法,我们不传参数默认提供的是less算法,意味着当父亲节点数据小于孩子节点数据时就会向上调整,那么构建出来就是大堆。当我们显示传递greater算法时,判断条件就变为当父亲节点数据大于孩子节点数据时才会进行调整,从而构建出小堆,同一个算法不同的参数就构成了两种不同的结果,极大提高了代码的复用率。

最后附上优先级队列的实现代码:

namespace lb
{
	//仿函数
	template<class T>
	struct less
	{
		bool operator() (const T& l, const T& r)const {return l < r;}
	};
	template<class T>
	struct greater
	{
		bool operator() (const T& l, const T& r)const {return l > r;}
	};
	template<class T, class Container = vector<T>,class Compare = std::less<T>>
	//默认大堆--小于--less//小堆--大于--greater
	class priority_queue
	{
	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--)
			{
				adjust_down(i);
			}
		}
		void push(const	T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}
		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}
		T& top() {return _con[0];}
		const T& top()const {return _con[0];}
		bool empty() {return _con.empty();}
		size_t size()const {return _con.size();}
		void adjust_up(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child>0)
			{
				//if (_con[child] > _con[parent])
				//if (_con[parent] < _con[child])
				if (com(_con[parent] , _con[child]))
				{
					std::swap(_con[child], _con[parent]);
					child = parent;
					parent= (child - 1) / 2;
				}
				else
					break;
			}
		}
		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				//if(child + 1 < _con.size() && _con[child+1] > _con[child])
				//if (child + 1 < _con.size() && _con[child ] < _con[child+1])
				if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
				{
					++child;
				}
				//if (_con[child] > _con[parent])
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
					break;
			}
		}
	private:
		Container _con;
	};
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bingbing~bang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值