C++容器适配器stack、queue和priority_queue模拟实现

文章目录

  • 一、适配器
  • 二、 浅谈deque
    • 2.1 vector和list的优劣势
    • 2.2 deque基本原理介绍
  • 三、stack的实现
  • 四、 queue的实现
  • 五、priority_queue模拟实现
    • 5.1 初识仿函数
    • 5.2 模拟实现priority_queue


一、适配器

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

其实就是一种可以用多种容器作为底层来实现的一种容器,比如栈和队列,它们本身不是一种容器,但是它们具有某种特性,可以用基础顺序容器来实现,比如vector和list。

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。可以看一下定义:
在这里插入图片描述

在这里插入图片描述
例如,我们可以显式传容器

int main()
{
	stack<int, vector<int>> st;//可以显示传入容器
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;
	return 0;
}

二、 浅谈deque

从上面可以看到,C++中默认栈和队列是用deque(双端队列),来作为底层容器实现的。那么deque是什么呢,有什么特性呢?

deque首先被弄出来的主要目的就是为了综合vector和list的优势。先来了解一下vector和list各自的优劣势

2.1 vector和list的优劣势

vector

优势:

  1. 支持高效的随机访问(最主要的优点)
  2. 尾插尾删效率高,CPU高速缓存命中率高(因为物理空间连续)

劣势

  1. 空间存在一定浪费,按需申请空间,导致频繁增容,效率低。
  2. 头插头删需要移动大量的数据,效率低

list

优势

  1. 按需申请和释放空间 ,空间都被利用。
  2. 任意点插入和删除效率高,O(1)
    劣势
  3. 不支持随机访问。(最大的劣势)

既然deque想要综合两个的优点,那么deque是什么结构,真的能替代上面两个吗?

2.2 deque基本原理介绍

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示
C++容器适配器stack、queue和priority_queue模拟实现_第1张图片
存储思想是,中控指针数组指向每个固定数组,从数组中间部分指针指向的数组开始存储,如果要头插,就往上一个数组的最后插入,尾插就往后继续插入。仍然不能解决中间插入删除的问题。

deque对比vector和list的优势

deque对比vector:扩容代价不大,不需要拷贝数据浪费空间也不多。
对比list:cpu高速cache命中。其次不会频繁申请小块空间。申请和释放空间次
数少代价低

deque虽然也可以做到随机访问,但是需要的代价比较大。它也不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list。
但是对应栈和队列这两种数据结构是非常适合的,它们不需要随机访问,只支持头尾操作,所以大佬们采用deque作为容器。

三、stack的实现

stack的使用很简单,这里就不在叙述了。直接模拟实现
C++容器适配器stack、queue和priority_queue模拟实现_第2张图片

#pragma once

namespace sqin
{
	template<class T, class Container = vector<T>>
	class stack
	{
	public:
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
		T& top()
		{
			return _con.back();
		}
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
	private:
		Container _con;
	};
	void test_stack()
	{
		stack<int, vector<int>> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);

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

}

四、 queue的实现

C++容器适配器stack、queue和priority_queue模拟实现_第3张图片

#pragma once

namespace sqin
{
	template<class T, class Container = list<T>>
	class queue
	{
	public:
		queue()
		{}//默认的构造函数即可,它会调用容器的构造函数
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		void push(const T& t)
		{
			_con.push_back(t);
		}
		void pop()
		{
			_con.pop_front();
		}
	private:
		Container _con;
	};
	void test_queue()
	{
		queue<int, list<int>> q;
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);

		while (!q.empty())
		{
			cout << q.front() << " ";
			q.pop();
		}
		cout << endl;
	}
}

五、priority_queue模拟实现

priority_queue文档
在这里插入图片描述

5.1 初识仿函数

STL库里面默认的排序算法是非递减排序,那如果我们要按非递增排序怎么办,不可能再写一个一样的函数,换一下比较符合吧。这个时候就需要仿函数来帮我们完成这件事,传入自己想要的比较方式。

什么是仿函数
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

例如库里面有大于和小于两个仿函数
在这里插入图片描述
在这里插入图片描述

C++容器适配器stack、queue和priority_queue模拟实现_第4张图片

5.2 模拟实现priority_queue

定义和测试

#pragma once

namespace sqin
{
	//优先级队列本质是堆
	//优先级大的再前面,是大堆,默认传小于的仿函数
	template<class T, class Container = std::vector<T>, class Compare = Less<T> >
	class priority_queue
	{
	
		
	private:
		Container _con;
	};
	void test_priority()
	{
		std::vector<int> v = { 2,4,5,6,7,1,3,8 };
		//priority_queue pq(v.begin(),v.end());
		priority_queue<int> pq;
		pq.push(2);
		pq.push(4);
		pq.push(5);
		pq.push(7);
		pq.push(6);
		pq.push(1);

		std::cout << pq.top() << std::endl;
		pq.pop();
		pq.pop();
	}
}

仿函数

	//自己写一个大于和小于仿函数
	template<class T>
	struct Less
	{
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

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

构造函数

priority_queue()
		{}

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first,last)//调用传入容器的迭代器构造函数
		{
			//建堆,向下调整法,找到最后一个有孩子结点的结点
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
			{
				adjust_down(i);
			}
		}

建堆相关函数

private:
		void adjust_up(size_t child)
		{
			Compare com;//实例化一个比较类,这样才可以访问函数
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void adjust_down(size_t parent)
		{
			Compare com;//实例化一个比较类,这样才可以访问函数
			//找到孩子结点中较大的结点 大堆,但是默认传入的是小于
			//所以需要注意比较次序
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
					child = child + 1;
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					//不符合直接退出,不需要继续比较
					break;
				}
			}
		}

主要函数

bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
		T& top()
		{
			return _con[0];
		}
		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);
		}

你可能感兴趣的:(C++,c++,开发语言,后端)