适配器模式适配出栈和队列及优先级队列

适配器模式适配出栈和队列及优先级队列

STL六大组件介绍

本文主要介绍如何用deque适配出栈和队列,那么deque是什么呢?

  • deque是STL中的一个容器,其具有vector一样的[ ]的重载,实现随机访问,同时也支持链表一样的头插尾插,是结合了vector和list的功能,看似非常牛逼,但实际上也是个纸老虎,其随机访问和头尾插的真实的效率也比不上vector和list,只能是说啥都会但是啥都不精通,因为其结构是由多个数组构成的,由一个指针数组存储每个数组的地址,来进行管理,因此其效率不是很可观。

img

deque 与 vector list 的对比
  • 与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

  • 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

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

img

deque的成员函数

与大多数容器一样,deque具有的主要功能有:头尾插入删除,任意位置插入删除,迭代器的支持,获取元素个数,判空等。这里不过多介绍,基本上会用vector或者list就能上手deque的基本使用了。

请添加图片描述

用deque适配出stack
STL中stack的主要功能

empty ():判空栈是否为空
size():获取元素个数
top():获取栈顶元素
push()(emplace()等同):将元素入栈
pop():删除栈顶元素
swap():两个栈相互交换(c++11支持)

在这里插入图片描述

我们清楚了deque和栈的成员函数(功能),可以发现stack的功能是否简单,是完全可以用deque来适配出来的。(通俗点就是用deque包装出stack)

代码很简单:

template<class T, class Container = deque<T> >//第一个模板参数是数据类型,第二个模板参数是适配容器
class stack
{
public:
	void push(const T& val)
	{
		_cn.push_back(val);//push数据就相当于deque的尾插
	}
	void pop()
	{
		_cn.pop_back();//相当尾删
	}
	const T& top()
	{
		return _cn.back();//栈顶元素就是空间的尾部数据
	}
	size_t size()
	{
		return _cn.size();//栈的元素个数就是deque的元素个数
	}

	bool empty()
	{
		return _cn.empty();//直接套用deque的判空即可
	}
private:
    //成员变量是适配容器
	Container _cn;

};
	
用deque适配出queue

与栈不同的是队列是先进先出的,而栈是后入先出的,那么这里的push和pop用deque实现的话就得是尾插和头删了。

还有就是队头元素的访问是调用的front()函数,队尾是调用back()函数。pop是删除队头元素。

template<class  T,class Container=deque<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			_cn.push_back(val);
		}
		void pop()
		{
			_cn.pop_front();//这里得用头删
		}
		const T& front()
		{
			return _cn.front();
		}
		size_t  size()
		{
			return _cn.size;
		}
		bool empty()
		{
			return _cn.empty();
		}
	private:
		Container _cn;
	};

在这里插入图片描述

用vector适配出优先级队列
  • 优先级队列的底层结构是一个堆,数组式的容器可以很方便的转化为堆,所以优先级队列可以用vector适配出来。
    在这里插入图片描述

回顾一下堆:

请添加图片描述

向上调整建堆

请添加图片描述

向下调整建堆

请添加图片描述

既然优先级队列的底层结构是堆 那么想要实现一个优先级队列就必须知道如何建立一个堆(大堆和小堆),而上面的向上和向下调整建堆的算法就必须掌握。

以建立小堆为例:

//向上调整 
void AjustUp(Container& cn,int child)
{
	//Compare cmp; 这是个对象,对象是一个()的重载函数 用来比较用的仿函数 下面会介绍这种方式
	int parent = child / 2;
	while (parent>=0)
	{
		if (_cn[child] < _cn[parent])
		{
			swap(_cn[child], _cn[parent]);
			child = parent;
			parent = child / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整
void AjustDown(Container& cn,size_t parent)
{
	//Compare cmp;
	int child = parent * 2+1;//左孩子
	while (child < _cn.size())
	{
		/if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
		{
			child = child + 1;
		}
		if (_cn[child] < _cn[parent])
		{
			swap(_cn[child], _cn[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


不支持仿函数的优先级队列

我们知道堆分大堆小堆,假如一组数据我们定义值越大优先级越高,那么就是在堆顶,这很明显就得建立大堆;反之,如果值越小的优先级越高那么就需要建小堆,那么如果不支持仿函数就必须写两种建堆的算法,也就是向上和向下调整算法都得写两个,但是它们仅仅是比较的符号不同,其他代码完全一致,显然这种方式不够好,代码冗余!!!

支持仿函数的优先级队列

当优先级支持仿函数后就不会有上面的代码冗余的问题了,我们可以通过显式的传仿函数给优先级队列,让其内部数据的比较按照我们自定义的仿函数的比较方式来比较,相反的比较方式,得到的堆的类型就是不同的,也就是说我们可以通过控制仿函数的类型实现优先级队列底层建的堆是大堆还是小堆。

仿函数的结构
//两种常见的仿函数,根据值的大小比较
template<class T>
struct LessCompare
{
	bool operator()(T& a,T& b)const //里面是一个()的重载函数
	{
		return a < b;
	}
};

template<class T>
struct GreaterCompare
{
	bool operator()(T& a, T& b)const
	{
		return a > b;
	}
};
使用方法和普通方法的比较
void test()
{
	int a=10;
	int b=29;
	//普通比较
    cout<<a>b<<endl;
    //仿函数比较
    LessCompare cmp;
    cout<< cmp(a,b) <<endl;//调用()的重载函数进行比较
}
支持仿函数的调整堆算法
//向上调整
void AjustUp(Container& cn,int child)
{
	Compare cmp;
	int parent = child / 2;
	while (parent>=0)
	{
		//if (_cn[child] < _cn[parent])
		if (cmp(_cn[child], _cn[parent]))//将传统的符号比较换成仿函数比较
		{
			swap(_cn[child], _cn[parent]);
			child = parent;
			parent = child / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整
void AjustDown(Container& cn,size_t parent)
{
	Compare cmp;
	int child = parent * 2+1;//左孩子
	while (child < _cn.size())
	{
		//if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
		if (child + 1 < _cn.size() && cmp(_cn[child + 1] , _cn[child]))
		{
			child = child + 1;
		}
		//if (_cn[child] < _cn[parent])
		if (cmp(_cn[child], _cn[parent]))
		{
			swap(_cn[child], _cn[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

掌握了上面两个调整堆算法,那么优先级队列的实现就已经完成了80%了!!!

成员函数

在这里插入图片描述

结构:
template<class T,class Container=vector<T>,class Compare=LessCompare<T>>
class priority_queue
{
public:
	void AjustDown(Container& cn,size_t parent);
	void AjustUp(Container& cn,int child);
	void push(const T& val);
	void pop();
	const T& top();
	size_t size();
	bool empty();
private:
	Container _cn;
};
模板的分析:
template<class T,class Container=vector<T>,class Compare=LessCompare<T>>

这里的模板有三个模板参数

  • 第一个参数很好理解就是存储的数据类型
  • 第二个模板参数就是依赖的容器类型这里给的缺省类型是vector
  • 第三个模板参数就是一个类(仿函数),用来方便比较数据,比较的地方就直接用该类里的()重载函数即可,该函数可以自定义,表示比较的方式可以由我们自定义,更加灵活可控。

成员函数的分析:

  • 结构里的前两个成员函数(调整算法前面已贴出)

  • push()函数就是往vector里尾插数据,然后调用向上调整算法维护堆的结构即可。

  • pop()函数就是将vector的首尾元素交换,将要删除的堆顶元素换到vector的尾部,再调用一次向下调整算法就可以维护堆的结构,最后再将vector的size- - 即可完成堆顶元素的删除。

  • top()函数就是返回堆顶元素,也就是返回vector的首元素了。

  • size()和empty()就直接调用vector的size()和empty()就行了。

整体代码
template<class T,class Container=vector<T>,class Compare=LessCompare<T>>
	class priority_queue
	{
	public:
		void AjustDown(Container& cn,size_t parent)
		{
			Compare cmp;
			int child = parent * 2+1;//左孩子
			while (child < _cn.size())
			{
				//if (child + 1 < _cn.size() && _cn[child + 1] < _cn[child])
				if (child + 1 < _cn.size() && cmp(_cn[child + 1] , _cn[child]))
				{
					child = child + 1;
				}
				//if (_cn[child] < _cn[parent])
				if (cmp(_cn[child], _cn[parent]))
				{
					swap(_cn[child], _cn[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}

		}
		 
		void AjustUp(Container& cn,int child)
		{
			Compare cmp;
			int parent = child / 2;
			while (parent>=0)
			{
				//if (_cn[child] < _cn[parent])
				if (cmp(_cn[child], _cn[parent]))
				{
					swap(_cn[child], _cn[parent]);
					child = parent;
					parent = child / 2;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& val)
		{
			_cn.push_back(val);
			AjustUp(_cn,_cn.size()-1);

		}
		void pop()
		{
			swap(_cn.front(), _cn.back());
			_cn.pop_back();
			AjustDown(_cn,0);
		}

		const T& top()
		{

			return _cn.front();
		}

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

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

	private:
		Container _cn;
	};
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值