1. stack的介绍、使用和实现

1.1 stack的介绍

stl里的stack其实和数据结构内的stack和前面数据结构的栈不能说百分百一样,但也有百分之90是一样的,他们的特性都是LIFO(last in first out)先进后出的原则,前面有类似的这里就不过多讲述;

这个是C++ stack的文档:stack - C++ Reference (cplusplus.com)


1.2 stack的使用

stack的主要的使用接口就那么几个,我们这里就直接上个主要的接口图和使用的代码;如下图和代码所示:

#include<iostream>
#include<stack>
using namespace std;

void TestStack()
{
	//同前面list一样,他们都是个模板
	stack<int> st;

	//empty的使用
	if (!st.empty())
	{
		cout << " stack is not empty" << endl;
	}
	else
	{
		cout << "stack is empty" << endl;
	}

	//push的使用
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);

	//empty的使用
	if (!st.empty())
	{
		cout << " stack is not empty" << endl;
	}
	else
	{
		cout << "stack is empty" << endl;
	}

	//size的使用
	cout << "The size of stack is:> " << st.size() << endl;

	//top的使用
	cout << "The top element of stack is:> " << st.top() << endl;

	//因为栈和队列没有迭代器,如果有迭代器的话那就可以访问任意位置的数据了,
	//不符合后进先出的原则
	//所以如果我们想打印栈内的数据需要这样走;
	cout << "The element of stack are:> ";
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}

	cout << endl;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);

	//pop的使用
	st.pop();
	st.pop();
	cout << "After pop the element of stack are:> ";
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
}

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

                                       

运行结果为:


1.3 stack的实现

由于在stl库内 stack不是一个简单的容器,而是一个容器适配器Container adaptor,举个例子,容器适配器就类似家里的插头,我们不仅可以用手机充电头去插头那给手机充电,还可以用电脑电源的充电头去给电脑充电,而手机充电头就类似list<int>,电脑充电头就类似vector<int>;如下代码可见;

#include<iostream>
#include<deque>
#include<stack>
#include<vector>
#include<list>
using namespace std;
//#include"stack.h"

void teststack()
{
	//适配list容器
	stack<int, list<int>> st1;
	st1.push(1);
	st1.push(2);
	st1.push(3);
	st1.push(4);
	st1.push(5);
	cout << "The top is:> " << st1.top() << endl;
	st1.pop();
	st1.pop();
	cout << "The top is:> " << st1.top() << endl;
	while (!st1.empty())
	{
		cout << st1.top() << " ";
		st1.pop();
	}

	cout << endl;
	//适配vector容器
	stack<int, vector<int>> st2;
	st2.push(1);
	st2.push(2);
	st2.push(3);
	st2.push(4);
	st2.push(5);
	cout << "The top is:> " << st2.top() << endl;
	st2.pop();
	st2.pop();
	cout << "The top is:> " << st2.top() << endl;
	while (!st2.empty())
	{
		cout << st2.top() << " ";
		st2.pop();
	}
}
int main()
{
	teststack();
	return 0;
}

                                       

运行结果为:

那么我们实现stack的时候我们的类模板就不止一个传入的类型,还有一个类类型的变量接收;那具体的实现步骤就直接看代码,因为这个实现的底层其实也就是数组,但也可以用链表去实现前面数据结构的栈的篇章说过;如下代码所示:

#pragma once
#include"priority_queue.h"
#include<deque>
namespace lwt
{
	//stack在stl库内是被称作container adaptor(容器适配器)
	template<class T, class Container = deque<T>>
	class stack
	{
	public:

		//尾插
		void push(const T& x)
		{
			_con.push_back(x);
		}
		//尾删
		void pop()
		{
			_con.pop_back();
		}

		//取栈顶元素
		const T& top()const
		{
			return _con.back();
		}

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

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

	private:
		Container _con;
	};
}


这里的成员变量用Container就是为了达到容器适配器的效果,如果传入的是vector<int>,那么就会执行vector<int> _con; 然后再进行插入删除,list也一样;那上面有一点就是deque是什么玩意,先保留这个疑问,下面一会会讲到;


2. queue的介绍、使用和实现

2.1 queue的介绍

stl里的queue和数据结构里的queue队列也类似,也是先进先出FIFO的特性,所以也不过多讲述;

这个是C++的queue的文档:queue - C++ Reference (cplusplus.com)


2.2 queue的使用

如下图和代码所示:

#include<iostream>
#include<deque>
#include<queue>
#include<stack>
#include<vector>
#include<list>
using namespace std;

int main()
{
	queue<int, list<int>> que1;
	que1.push(1);
	que1.push(2);
	que1.push(3);
	que1.push(3);
	que1.push(4);
	que1.push(5);
	while (!que1.empty())
	{
		cout << que1.front() << " ";
		que1.pop();
	}
	cout << endl;
	//size的使用
	que1.push(1);
	que1.push(2);
	que1.push(3);
	que1.push(3);
	que1.push(4);
	que1.push(5);
	cout << "The size of que1 is:> " << que1.size() << endl;
	que1.front() = 9;
	cout << "The front of que1 is:> " << que1.front() << endl;


	return 0;
}

                                       

运行结果为:


2.3 queue的实现

stl库内的queue和上面的stack一样,都是容器适配器;增删查改实现的话其实就和前面的数据结构的队列实现的思路一样,所以不在这里过多讲述,直接上图和代码;

#pragma once
namespace lwt
{
	#include<deque>
	template<class T, class Container = deque<T> >
	class queue
	{
	public:
		//入数据
		void push(const T& x)
		{
			_con.push_back(x);
		}

		//出队列
		void pop()
		{
			_con.pop_front();
		}

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

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

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

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

	private:
		Container _con;
	};
}

3. deque的介绍

在上面我们看到stack和queue的模板的类类型的缺省参数是deque,但上面没有讲,因为我想在下面另外讲解;deque,可能我们看到有个que就认为他就是队列,所以底层可能是用链表实现的,其实并不然,deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,也可以说deque是list和vector的杂交版;

但实质上,其实deque并不是真正的连续的空间,而是由一段段连续的小空间拼排而成的,实际上deque就类似一个动态的二维数组;其结构如下图所示:

红色区域是一个中控数组,每次的插入和删除都是在中控数组的中间开空间,这个效率就很高了,为什么高呢?相比于链表,他的尾插和头插的效率更高,因为他每次都是开绿色块那样的空间大小,而链表每次头尾插需要进行开辟新的空间,开辟新的空间会造成效率降低;对于vector来说,头插就很方便了,他只需要在前面再new多一块空间,然后从后往前插入完成头插,而不需要挪动数据,

下图是他的具体结构

3.1 deque的缺陷

当deque遍历的时候要频繁的检测first是否等于last,如果等于就需要node+1,导致效率低下,所以一般要使用线性结构的时候大多数优先考虑list和vector,deque的应用场景不多,目前看见的应用是在STL里用其作为stack和queue的底层结构;

3.2 为什么选择deque作为stack和queue的底层结构

1.  stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进
行操作。
2.  在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的
元素增长时,deque不仅效率高,而且内存使用率高。

4. priority_queue的介绍和实现

4.1 priority_queue的介绍

        priority_queue又称优先级队列,它的第一个元素总是它所包含的元素中最大的,那这是不是就有点像我们前面数据结构学过的堆;其实优先级队列的底层就是堆,首先我们先来回忆一下堆的性质,堆分为大堆和小堆,大堆的每个树的父节点是树中最大的,小堆则反之,但是STL的priority_queue和我们数据结构那实现的堆的不同是,他是一个容器适配器,即只要符合他的特性的容器都可以拿来当他的底层,如下图就是优先级队列的特性;

而标准类vector和deque满足这些条件;还需要支持随机访问迭代器,因为完成建堆等操作,而 堆的底层就是顺序表,那就需要支持随机访问迭代器;


4.2 仿函数基础

在实现优先级队列之前我们还需要提一个点就是仿函数,仿函数顾名思义就是类似函数的东西,他实质上是一个类,他是重载了“()”的一个类,作用是用来做比较,例如如果我们想实现一个大堆的话,我们就需要修改建堆的大于小于号,那是不是太麻烦了,所以我们就延伸出了仿函数这个东西;举个例子,比如说我想让条件为arr[parent]>arr[child]的时候我们就可以定义一个类作为仿函数greater(arr[parent], arr[child] ),而我我想让条件改为arr[parent]<arr[child]的话就定义一个类作为仿函数less(arr[parent], arr[child];如下代码可见,这是使用,就需要在创建priority_queue的时候在最后面加一个greater<int>;

#include <vector>
#include <queue>
#include<iostream>
#include <functional> // greater算法的头文件
using namespace std;

void TestPriorityQueue()
{
	// 默认情况下,创建的是大堆,其底层按照小于号比较
	vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };
	priority_queue<int> q1;
	for (auto& e : v)
		q1.push(e);
	cout << q1.top() << endl;
	// 如果要创建小堆,将第三个模板参数换成greater比较方式
	priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
	cout << q2.top() << endl;
}

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

4.2 priority_queue的实现

优先级队列的实现也很简单,其实就是建堆,只不过在这里面添加多了一个仿函数进行判断是建小堆还是大堆,如下代码所示:

#pragma once
#include<vector>
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 lwt
{
	template<class T, class Container = vector<T> , class Compare = Less<T>>
	class priority_queue
	{
	public:
		void AdjustUp(int child)
		{
			Compare  com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;	
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);

			AdjustUp(_con.size()-1);
		}

		void AdjustDown(int parent)
		{
			size_t child = parent * 2 + 1;

			Compare com;
			while (child < _con.size())
			{
				//看左右孩子谁大谁小,大堆那我们就要找到小的
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void pop()
		{
			//pop就是堆删
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

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

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

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

}

END!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部