C++STL—deque、stack、queue、priority_queue介绍使用和模拟实现

C++教学总目录

1、deque

在这里插入图片描述
deque是双端队列,我们学习的队列是先进先出的(First in first out),相比于队列,双端队列是支持头插头删、尾插尾删的。包含于头文件<deque>

我们之前学的vector实际上就是顺序表,它重载了operator[],所以可以随意访问任意一个位置的元素,但是如果扩容需要挪动数据,消耗较大,并且头插头删/中间插入删除都需要挪动数据。
我们之前学的list是带头双向循环链表,它在任意位置的插入删除效率很高,并且不存在扩容问题,按需申请按需释放,但是不支持下标随机访问。
vector和list各有优缺点:而deque双端队列就是结合了vector和list的优点设计出来的,它支持任意位置的下标访问,也支持头插头删/尾插尾删。

下面这张图描述了deque的底层实现原理:
在这里插入图片描述


下面看看deque的接口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用法基本上同之前的vector,这里就不再赘述。由于deque的底层实现比较复杂,我们不模拟实现,有兴趣可以去看看库里是如何实现的。

2、stack使用介绍

在这里插入图片描述
栈是后进先出的一种数据结构,STL中的stack是一种容器适配器,包含于头文件<stack>,stack也是类模板,有两个模板参数,第一个T代表数据类型,第二个Container表示容器,模板参数也可以给缺省值,这里默认用deque来作为stack的容器。

下面是stack的核心接口:
主要是:1、size返回栈中元素个数。2、empty判断栈是否为空。3、top返回栈顶元素。4、push元素入栈。5、pop弹出栈顶元素。

在这里插入图片描述
stack是没有迭代器的,不能使用迭代器/范围for进行遍历,可以用下面方式遍历:

在这里插入图片描述


上面模板参数我们只给了类型,所以默认stack的底层容器就是deque,假设我不想用deque呢?我可以传vector或list:

stack<int, list<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
while (st.size())
{
	cout << st.top() << " ";
	st.pop();
}

stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
while (st.size())
{
	cout << st.top() << " ";
	st.pop();
}

3、stack实现

我们直接使用deque作为stack的默认容器:

#pragma once

namespace zzy
{
	template<class T, class 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();
		}

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

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

4、queue使用介绍

在这里插入图片描述
队列的性质是先进先出,STL中的queue和stack一样,都是容器适配器,默认底层容器也是deque。包含于头文件<queue>

下面给出核心接口:1、empty判断队列是否为空。2、size返回队列元素个数。3、front返回队头元素。4、back返回队尾元素。5、push在队尾插入元素。6、pop弹出队头元素。
在这里插入图片描述
queue也是没有迭代器的。用法如下:

在这里插入图片描述
和上面stack一样,如果不想使用deque作为queue的底层容器,可以自己传模板参数。

5、queue实现

#pragma once

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

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

6、priority_queue使用介绍

在这里插入图片描述
priority_queue是优先级队列(堆),它也是容器适配器,包含于头文件<queue>
可以看到这里有三个模板参数,第三个是仿函数——用来控制比较大小从而实现大堆/小堆。

下面看看优先队列的接口:
在这里插入图片描述
1、empty判断堆是否为空。2、size计算堆中元素个数。3、top获取堆顶元素。4、push向堆中插入元素并调整堆。5、pop弹出堆顶元素并调整堆。

用法如下:
在这里插入图片描述
如果要用大堆,可以传仿函数的模板参数:

priority_queue<int, vector<int>, greater<int>> pq;
pq.push(10);
pq.push(24);
pq.push(17);
pq.push(30);
pq.push(1);
while (pq.size())
{
	cout << pq.top() << " ";
	pq.pop();
}

7、priority_queue实现

仿函数/函数对象:这个类对象可以像函数一样使用——实际上是重载了operator()
看下面的类:

template<class T>
struct Less
{
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

int main()
{
	Less<int> lessfunc;
	cout << lessfunc(1, 2) << endl;  // 像函数一样使用
	cout << lessfunc.operator()(1, 2) << endl; // 实际上是重载了operator()
	return 0;
}

这就是仿函数。官方实现了less和greater两个仿函数,less比较就是<,而greater就是>。
针对内置类型,我们可以直接使用less和greater。如果是自定义类型,也可以使用less和greater,但是要保证自定义类型中重载了operator</operator>。
另外有些场景我们需要自己实现仿函数,例如存放的是指针类型。

下面是priority_queue的实现:

#pragma once

template<class T>
class Less
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

template<class T>
class Greater
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};

namespace zzy
{
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	private:
		void AdjustDown(int parent)
		{
			Compare com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else break;
			}
		}

		void AdjustUp(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else break;
			}
		}
	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 >> 1; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}
		
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}

		const T& top()
		{
			return _con[0];
		}

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

		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};

	void test_priority_queue1()
	{
		priority_queue<int, vector<int>, Greater<int>> pq;

		pq.push(3);
		pq.push(5);
		pq.push(1);
		pq.push(4);

		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}

	class Date
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}

		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		friend ostream& operator<<(ostream& _cout, const Date& d);
	private:
		int _year;
		int _month;
		int _day;
	};

	ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

	struct LessPDate
	{
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	void test_priority_queue2()
	{
		// 仿函数控制实现小堆
		//priority_queue<Date, vector<Date>, greater<Date>> pq;
		//pq.push(Date(2023, 7, 20));
		//pq.push(Date(2023, 6, 20));
		//pq.push(Date(2023, 8, 20));

		//while (!pq.empty())
		//{
		//	cout << pq.top() << " ";
		//	pq.pop();
		//}
		//cout << endl;

		priority_queue<Date*, vector<Date*>, LessPDate> pq;
		pq.push(new Date(2023, 7, 20));
		pq.push(new Date(2023, 6, 20));
		pq.push(new Date(2023, 8, 20));

		while (!pq.empty())
		{
			cout << *pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}
}

关于模板中的仿函数有一个注意点:
在这里插入图片描述

8、反向迭代器

反向迭代器的实现需要创建自定义类型,反向迭代器是一种适配器,通过对正向迭代器的封装来实现。
在这里插入图片描述
如图:反向迭代器的rbegin就是正向迭代器的end,反向迭代器的rend就是正向迭代器的begin,之所以这么设计是考虑了镜像对称。
因此,反向迭代器的++就是正向迭代器的–,反向迭代器的–就是正向迭代器的++。由于反向迭代器与容器中元素错位,所以实现operator*的时候需要–再解引用获取元素。

下面先给出模板参数:

template<class Iterator, class T, class Ref, class Ptr>

第一个模板参数是正向迭代器,后面两个Ref和Ptr是为了通过不同的参数实例化出普通反向迭代器和const反向迭代器。之所以还需要给一个T类型的模板参数是因为需要实现普通反向迭代器构造const反向迭代器的函数,否则无法使用const反向迭代器。

template<class Iterator, class T, class Ref, class Ptr>
struct ReverseIterator
{
	typedef ReverseIterator<Iterator, T, Ref, Ptr> self;
	typedef ReverseIterator<T*, T, T&, T*> reverseiterator;

	Iterator _it;

	ReverseIterator(Iterator it)
		:_it(it)
	{}

	ReverseIterator(const reverseiterator& it)
		:_it(it._it)
	{}

	Ref operator*()
	{
		Iterator tmp = _it;
		return *(--tmp);
	}

	Ptr operator->()
	{
		return &(operator*());
	}

	self& operator++()
	{
		--_it;
		return *this;
	}
	
	self operator++(int)
	{
		self tmp(_it);
		--_it;
		return tmp;
	}

	self& operator--()
	{
		++_it;
		return *this;
	}

	self& operator--(int)
	{
		self tmp(_it);
		++_it;
		return tmp;
	}

	bool operator!=(const self& s) const
	{
		return _it != s._it;
	}

	bool operator==(const self& s) const
	{
		return _it == s._it;
	}
};

然后在容器中加入以下代码:

typedef ReverseIterator<iterator, T, T&, T*> reverse_iterator;
typedef ReverseIterator<iterator, T, const T&, const T*> const_reverse_iterator;

reverse_iterator rbegin() { return end(); }
reverse_iterator rend() { return begin(); }
const_reverse_iterator rbegin() const { return end(); }
const_reverse_iterator rend() const { return begin(); }

以上是实现方式之一,对于之前写的string和vector都可以使用。库里的实现方式是迭代器萃取,有兴趣可以了解一下。
const反向迭代器有点问题,后续改善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值