stack,queue,priority_queue的模拟实现及常用接口

目录

1.stack和queue应用

1.1stack的使用

1.2stack的模拟实现

1.3queue的使用

1.4queue的模拟实现

2.容器适配器

2.1什么是适配器

[2.2 deque介绍](#2.2 deque介绍)

[2.3 list和vector的区别](#2.3 list和vector的区别)

3.priority_queue

3.1priority_queue的使用

[3.2 priority_queue的模拟实现](#3.2 priority_queue的模拟实现)

[3.3 仿函数](#3.3 仿函数)


1.stack和queue应用

stack(栈):先进后出

queue(队列):先进先出

1.1stack的使用

|---------|------------|
| 函数说明 | 接口说明 |
| stack() | 构造空栈 |
| empty | 判断是否为空 |
| size | 返回栈中有效数据个数 |
| top | 返回栈顶元素 |
| push | 将元素val压入栈中 |
| pop | 将栈顶元素弹出 |

cpp 复制代码
#include<stack>
#include<iostream>
using namespace std;
int main()
{
	stack<int> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	while (!s1.empty())
	{
		cout << s1.top() << " ";
		s1.pop();
	}
	return 0;
}

1.2stack的模拟实现

对于stack的模拟实现,从栈的接口中可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;
namespace chuxin
{
	template<class T>
	class stack
	{
	public:
		stack()
		{}
		void push(const T& x)
		{
			_s.push_back(x);
		}
		void pop() { _s.pop_back(); }
		T& top() { return _s.back(); }
		const T& top()const { return _s.back(); }
		size_t size()const { return _s.size(); }
		bool empty()const { return _s.empty(); }
	private:
		std::vector<T> _s;
	};
}

1.3queue的使用

|---------|-----------------------------------------------|
| 函数说明 | 接口说明 |
| queue() | 构造空的队列 |
| empty | 检测队列是否为空,是返回 true ,否则返回 false |
| size | 返回队列中有效元素的个数 |
| front | 返回队头元素的引用 |
| back | 返回队尾元素的引用 |
| push | 在队尾将元素 val 入队列 |
| pop | 将队头元素出队列 |

cpp 复制代码
#include<queue>
#include<iostream>
using namespace std;
int main()
{
	queue<int> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	cout << "s1.back()"<<" :";
	while (!s1.empty())
	{
		cout<< s1.back() << " ";
		
		s1.pop();
	}
	cout << endl;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	cout << "s1.front()" << " :";
	while (!s1.empty())
	{
		cout <<s1.front()<<" ";
		s1.pop();
	}
	return 0;
}

1.4queue的模拟实现

因为 queue 的接口中存在头删和尾插,因此使用 vector 来封装效率太低,故可以借助 list 来模拟实
现 queue

cpp 复制代码
#include <list>
namespace chuxin
{
	template<class T>
	class queue
	{
	public:
		queue() {}
		void push(const T& x) { _c.push_back(x); }
		void pop() { _c.pop_front(); }
		T& back() { return _c.back(); }
		const T& back()const { return _c.back(); }
		T& front() { return _c.front(); }
		const T& front()const { return _c.front(); }
		size_t size()const { return _c.size(); }
		bool empty()const { return _c.empty(); }
	private:
		std::list<T> _c;
	};
}

2.容器适配器

2.1什么是适配器

适配器是一种设计模式 ( 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设
计经验的总结 ) ,该种模式是将一个类的接口转换成客户希望的另外一个接口, 虽然 stack 和 queue 中也可以存放元素,但在 STL 中并没有将其划分在容器的行列,而是将其称为 容器适配器 ,这是因为 stack 和队列只是对其他容器的接口进行了包装, STL 中 stack 和 queue 默认 使用 deque, 对于stack,queue在上文中,容器的类型已经写死了,stack只能用vector类初始化,queue只能用list初始化,此时就可以利用模版进行改动
stack:

cpp 复制代码
#include<iostream>
#include<deque>
using namespace std;
namespace chuxin
{
	template<class T, class Container = deque<T>>//适配多种容器
	class stack
	{
	public:
		stack()
		{}
		void push(const T& x)
		{
			_s.push_back(x);
		}
		void pop() { _s.pop_back(); }
		T& top() { return _s.back(); }
		const T& top()const { return _s.back(); }
		size_t size()const { return _s.size(); }
		bool empty()const { return _s.empty(); }
	private:
		Container _s;
	};
}

queue:

cpp 复制代码
#include <deque>
#include<iostream>
using namespace std;
namespace chuxin
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		queue() {}
		void push(const T& x) { _c.push_back(x); }
		void pop() { _c.pop_front(); }
		T& back() { return _c.back(); }
		const T& back()const { return _c.back(); }
		T& front() { return _c.front(); }
		const T& front()const { return _c.front(); }
		size_t size()const { return _c.size(); }
		bool empty()const { return _c.empty(); }
	private:
		Container _c;
	};
}

2.2 deque介绍

在上文,发现stack和queue容器的缺省值是deque,为什么用这个容器呢?

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

stack 是一种后进先出的特殊线性数据结构,因此只要具有 push_back() 和 pop_back() 操作的线性结构,都可以作为stack 的底层容器,比如 vector 和 list 都可以
queue 是先进先出的特殊线性数据结构,只要具有push_back 和 pop_front 操作的线性结构,都可以作为 queue 的底层容器,比如list。
但是 STL 中对 stack 和 queue 默认选择 deque 作为其底层容器,主要是因为:

  1. stack 和 queue 不需要遍历 ( 因此 stack 和 queue 没有迭代器 ) ,只需要在固定的一端或者两端进行操作。
  2. 在 stack 中元素增长时, deque 比 vector 的效率高 ( 扩容时不需要搬移大量数据 ) ; queue 中的
    元素增长时, deque 不仅效率高,而且内存使用率高。
    对于deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于动态的二维数组,deque的插入,删除则涉及它的迭代器,由于deque的迭代器较复杂本文不做叙述
    缺陷:
    与 vector 比较 , deque 的优势是:头部插入和删除时, 不需要搬移元素,效率特别高 ,而且在 扩容时,也不需要搬移大量的元素,因此其效率是必 vector 高的。
    与 list 比较 ,其底层是连续空间, 空间利用率比较高 ,不需要存储额外字段。
    但是, deque 有一个致命缺陷:不适合遍历,因为在遍历时, deque 的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此 在实 际中,需要线性结构时,大多数情况下优先考虑vector 和 list , deque 的应用并不多,而 目前能看到的一个应用就是,STL 用其作为 stack 和 queue 的底层数据结构 。

2.3 list和vector的区别

vector 与 list 都是 STL 中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及
应用场景不同,其主要不同如下:

|-------------------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
| | vector | list |
| | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 访 | 支持随机访问,访问某个元素效率 O(1) | 不支持随机访问,访问某个元素效率 O(N) |
| | 任意位置插入和删除效率低,需要搬移元素,时间 复杂度为 O(N) ,插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 O(1) |
| | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 O(1) | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 |
| | 原生态指针 | 对原生态指针 ( 节点指针 ) 进行 封装 |
| | 在插入元素时,要给所有的迭代器重新赋值,因为 插入元素有可能会导致重新扩容,致使原来迭代器 失效,删除时,当前迭代器需要重新赋值否则会失 | 插入元素不会导致迭代器失 效,删除元素时,只会导致当 前迭代器失效,其他迭代器不 受影响 |
| 使 | 需要高效存储,支持随机访问,不关心插入删除效 | 大量插入和删除操作,不关心 随机访问 |

3.priority_queue

3.1priority_queue的使用

priority_queue的使用和queue的一样,不同的是他们的底层的一些结构,以及处理数据不同,
优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 元素构造成堆的结构,因此 priority_queue 就是堆,所有需要用到堆的位置,都可以考虑使用 priority_queue 。注意:默认情况下 priority_queue 是大堆

|-----------------------------|--------------------------------|
| 函数声明 | 接口说明 |
| priority_queue() | 构造一个空的优先级队列 |
| priority_queue(first, last) | 利用迭代器区间建立 |
| empty | 检测优先级队列是否为空,是返回true,否 则返回false |
| top | 返回优先级队列中最大(最小元素),即堆顶元 素 |
| push | 在优先级队列中插入元素x |
| pop | 删除优先级队列中最大(最小)元素,即堆顶元 素 |

cpp 复制代码
#include<queue>
#include<iostream>
#include<list>
using namespace std;
int main()
{
	priority_queue<int> s1;
	s1.push(5);
	s1.push(3);
	s1.push(7);
	s1.push(2);
	s1.push(6);
	while (!s1.empty())
	{
		cout <<s1.top()<<" ";
		s1.pop();
	}
	return 0;
}

3.2 priority_queue的模拟实现

priority_queue的底层就是一个堆,因此它的模拟实现,和堆的类似

cpp 复制代码
#include<vector>
namespace chuxin
{
	// 默认是大堆
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_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;

			
			while (child < _con.size())  // child >= n说明孩子不存在,调整到叶子了
			{
				// 找出小的那个孩子
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}

				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void 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;
	};
}

这种方法还是有缺陷,发现只能排降序,有没有一种方法既能排升序又能排降序?这时候就需要仿函数了

3.3 仿函数

来看看下面这段冒泡排序

cpp 复制代码
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		// 单趟
		int flag = 0;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i] < a[i - 1])
			{
				swap(a[i - 1], a[i]);
				flag = 1;
			}
		}

		if (flag == 0)
		{
			break;
		}
	}
}

发现它的逻辑是排一个升序,如果要排一个降序怎么办,为了一个比较重载一个大部分相似的函数,显然不怎么好,而且比较的数据可能不同,有时候比较整形,有时候是浮点型,这时候就可以利用模版自己写一个比较函数,而这个函数称为仿函数

cpp 复制代码
// 仿函数:本质是一个类,这个类重载operator(),他的对象可以像函数一样使用
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;
	}
};
template<class Compare>
void BubbleSort(int* a, int n, Compare com)
{
	for (int j = 0; j < n; j++)
	{
		// 单趟
		int flag = 0;
		for (int i = 1; i < n - j; i++)
		{
			//if (a[i] < a[i - 1])
			if (com(a[i], a[i - 1]))
			{
				swap(a[i - 1], a[i]);
				flag = 1;
			}
		}

		if (flag == 0)
		{
			break;
		}
	}
}
int main()
{
	Less<int> LessFunc;
	Greater<int> GreaterFunc;
	// 函数对象
	cout << LessFunc(1, 2) << endl;
	cout << LessFunc.operator()(1, 2) << endl;

	int a[] = { 9,1,2,5,7,4,6,3 };
	BubbleSort(a, 8, LessFunc);
	BubbleSort(a, 8, GreaterFunc);

	BubbleSort(a, 8, Less<int>());
	BubbleSort(a, 8, Greater<int>());
	return 0;
}

此时就发现,改变是否是升降序,就可以利用两个类对象就可以做到,此时priority_queue的比较就是这样

cpp 复制代码
#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 chuxin
{
	// 默认是大堆
	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 (_con[parent] < _con[child])
				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())  // child >= n说明孩子不存在,调整到叶子了
			{
				// 找出小的那个孩子
				//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void 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;
	};
}
相关推荐
Wendy14417 小时前
【线性回归(最小二乘法MSE)】——机器学习
算法·机器学习·线性回归
拾光拾趣录7 小时前
括号生成算法
前端·算法
渣呵8 小时前
求不重叠区间总和最大值
算法
拾光拾趣录8 小时前
链表合并:双指针与递归
前端·javascript·算法
好易学·数据结构8 小时前
可视化图解算法56:岛屿数量
数据结构·算法·leetcode·力扣·回溯·牛客网
香蕉可乐荷包蛋9 小时前
AI算法之图像识别与分类
人工智能·学习·算法
tomato099 小时前
河南萌新联赛2025第(一)场:河南工业大学(补题)
c++·算法
墨染点香10 小时前
LeetCode Hot100【5. 最长回文子串】
算法·leetcode·职场和发展
甄卷11 小时前
李沐动手学深度学习Pytorch-v2笔记【08线性回归+基础优化算法】2
pytorch·深度学习·算法