【C++】——Stack与Queue(含优先队列(详细解读)

news2024/10/7 10:20:22

前言

之前数据结构中是栈和队列,我们分别用的顺序表和链表去实现的,但是对于这里的栈和队列来说,他们是一种容器,更准确来说是一种容器适配器

✨什么是容器适配器?

 从他们的模板参数可以看出,第二个参数模板是一个容器,并不像vector和list一样是一个内存池

至于deque后面会介绍

这里写传容器的原因就是,栈和队列是直接复用了其他的容器,用他们的功能实现自己的功能,就比如每家的电压和自己手机充电器的电压是不一样的,但是我用一个适配器就可以转换出来

 

一  Stakc的介绍和使用

1.1 Stakc介绍

  1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
  2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
  3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
  4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

Stack是没有迭代器的,因为栈不能说是去遍历每个元素,而是后进先出的结构,所以设计迭代器没有任何意义,虽然可以去设计

 

1.2  栈的使用 

 对于之前数据结构来说,我们需要自己写一个栈,这里就直接引入头文件就行。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<stack>
using namespace std;
int main()
{
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);
	int n = st.size();
	while (n--)
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;
}

 

这里只是一个简单的演示

下面来做一个题目:

155. 最小栈

提示

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示:

  • -231 <= val <= 231 - 1
  • poptop 和 getMin 操作总是在 非空栈 上调用
  • pushpoptop, and getMin最多被调用 3 * 104 次

 思路:

对于这道题目来说,要拿出最小的那个元素,那么我们必须去记录,如果我们用一个变量去保存这个最小值肯定是不行的,因为我们用这个变量保存一个最小值以后,然后我们再删这个最小值,那当前最小就不知道是谁了,得重新来一遍,这样虽然可行,但是在时间效率上可能会很低

所以解决这道题可以用两个栈去模拟,一个栈存最小值,一个栈做为入栈,只要比最小栈的最小元素还小的时候(就是栈顶元素)就可以加入到这个最小栈里面,我们删除数据的时候,如果删除的数据和栈顶元素相等,那么我们把最小栈的元素删除,否则不动。

有了思路,现在来写代码就简单很多了

class MinStack {
public:
    MinStack() {

    }
    
    void push(int val) {
        _elem.push(val);//先入
        if(_min.empty()||val<=_min.top())//如果最小栈是空或者当前值比最小栈的栈顶元素还小就加入
        {
            _min.push(val);
        }
    }
    
    void pop() {
        if(_elem.top()==_min.top())//相等就一起删,最后最小栈栈顶元素就是最小的
        {
            _min.pop();
        }
        _elem.pop();
    }
    
    int top() {
        return _elem.top();//其他函数按规则来就行
    }
    
    int getMin() {
        return _min.top();
    }
    private:
    std::stack<int> _elem;//设置两个栈
    std::stack<int> _min;
    
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

  二  Queue的介绍和使用

2.1 队列的介绍

1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作 :
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

 

2.2  队列的使用 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<stack>
#include<queue>
using namespace std;
int main()
{
	queue<int>q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	int n = q.size();
	while (n--)
	{
		cout << q.front() << " ";
		q.pop();
	}
	cout << endl;
	return 0;
}

三  优先级队列的介绍和使用

3.1 优先队列介绍

   1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
    2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元 素)。
    3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
    4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
    empty():检测容器是否为空
    size():返回容器中有效元素个数
    front():返回容器中第一个元素的引用
    push_back():在容器尾部插入元素 

    pop_back():删除容器尾部元素
   5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

优先队列有三个模板参数,一个是 数据类型,一个是容器,一个是比较模板

到这里我们可以得知优先队列默认就是一个大堆

其中比较这里的比较采用的是模板,并不是之前的函数,但是这里可以用比较函数去进行,也就是用一个函数指针,但是c++不喜欢这种做法,并引伸出了仿函数

✨仿函数

仿函数就是把一个写成函数的形式

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

这里是一个比较大小的仿函数,同时也是一个模板类型, 如果确定类型也可以不用写成模板类型

我们可以直接用这个作为比较函数,完成比较。

3.2 优先队列的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<stack>
#include<queue>
#include <functional>
using namespace std;
// greater算法的头文件
int main()
{
	// 默认情况下,创建的是大堆,其底层按照小于号比较
	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;
	return 0;
}
	

优先队列优先是大堆,这里第三个模板参数的改变也就意味比较函数的改变

四 三种容器的模拟实现

4.1 stack模拟实现

#pragma once
template<class T,class Container=deque<T>>
class Stack
{
public:
	Stack()
	{}
	void push(const T&x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_back();
	}

	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
	const T& top()const
	{
		return _con.back();
	}
	T& top()
	{
		return _con.back();
	}

private:
	Container _con;
};

4.2  queue模拟实现

 

#pragma once
template<class T,class Container=deque<T>>
class Queue
{
public:
	void push(const T&x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_front();
	}
	size_t size()
	{
		return _con.size();
	}
	bool empty()
	{
		return _con.empty();
	}
	T& front()
	{
		return _con.front();
	}
	const T& front()const 
	{
		return _con.front();
	}
	T& back()
	{
		return _con.back();
	}
	const T& back()const
	{
		return _con.back();
	}
private:
	Container _con;
};

4.3  优先队列的模拟实现

#pragma once
template<class T,class Container=vector<T>>
class Priority_queue
{
public:
	Priority_queue()//无参构造
	{}
	//迭代器构造
	template<class InputIterator>
	Priority_queue(InputIterator first, InputIterator last):_con(first,last)
	{
		for (int i = (_con.size() - 1) / 2; i >= 0; i--)
		{
			adjust_down(i);
		}
	}
	//向上调整
	void adjust_up(int child)
	{
		int father = (child-1)/2;
		while (child > 0)
		{
			if (_con[father] < _con[child])
			{
				swap(_con[child], _con[father]);
				child = father;
				father = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
	//向下调整
	void adjust_down(int father)
	{
		int child = father * 2 + 1;
		while (child < _con.size())
		{
			if (child + 1 < _con.size() && _con[child] < _con[child + 1])
			{
				child++;
			}
			if (_con[father] < _con[child])
			{
				swap(_con[father], _con[child]);
				father = child;
				child = father * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		adjust_up(_con.size() - 1);
	}
	void pop()
	{
		swap(_con[0], _con[_con.size() - 1]);
		_con.pop_back();
		adjust_down(0);
	}
	const T& top()
	{
		return _con[0];
	}
	bool empty()
	{
		return _con.empty();
	}
	size_t size()
	{
		return _con.size();
	}
private:
	Container _con;//容器对象
};

测试

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<stack>
#include<queue>
#include <functional>
using namespace std;
#include"priority_queue.h"
int main()
{
	vector<int>v = { 3,4,5,1,8,7,8 };
	Priority_queue<int>p(v.begin(), v.end());
	int n = p.size();
	while (n--)
	{
		cout << p.top() << " ";
		p.pop();
	}
	cout << endl;
	return 0;
}

 

 

上面的模拟构造并没有使用第三个参数,也就是没有仿函数,优先队列其实是复用了vector容器,看似是堆,但内部结构是一个可变的数组,其中的每个操作步骤也就是在操作数组里面的元素

还有一个加入仿函数的例子

#pragma once
template<class T,class Container=vector<T>,class Compare=Less<T>>
class Priority_queue
{
public:
	Priority_queue()//无参构造
	{}
	//迭代器构造
	template<class InputIterator>
	Priority_queue(InputIterator first, InputIterator last):_con(first,last)
	{
		for (int i = (_con.size() - 1) / 2; i >= 0; i--)
		{
			adjust_down(i);
		}
	}
	//向上调整
	void adjust_up(int child)
	{
		int father = (child-1)/2;
		while (child > 0)
		{
			if (_com(_con[father] , _con[child]))
			{
				swap(_con[child], _con[father]);
				child = father;
				father = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
	//向下调整
	void adjust_down(int father)
	{
		int child = father * 2 + 1;
		while (child < _con.size())
		{
			if (child + 1 < _con.size() && _com(_con[child] , _con[child + 1]))
			{
				child++;
			}
			if (_com(_con[father] , _con[child]))
			{
				swap(_con[father], _con[child]);
				father = child;
				child = father * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		adjust_up(_con.size() - 1);
	}
	void pop()
	{
		swap(_con[0], _con[_con.size() - 1]);
		_con.pop_back();
		adjust_down(0);
	}
	const T& top()
	{
		return _con[0];
	}
	bool empty()
	{
		return _con.empty();
	}
	size_t size()
	{
		return _con.size();
	}
private:
	Container _con;//容器对象
	Compare _com;//仿函数对象
};

 注意模板参数的变化和比较大小代码的变化,仿函数是写在外面的,具体怎么比,按自己的需求来

测试:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<stack>
#include<queue>
#include <functional>
using namespace std;
#include"priority_queue.h"

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;
	}
};
int main()
{
	vector<int>v = { 3,4,5,1,8,7,8 };
	Priority_queue<int,vector<int>,Greater<int>>  p(v.begin(), v.end());
	int n = p.size();
	while (n--)
	{
		cout << p.top() << " ";
		p.pop();
	}
	cout << endl;
	return 0;
}

 

 五  deque

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

 和vector和list比起来,它比vector头插的效率高,和list比,它支持随机访问,且空间利用率比list高,但是它都无法替代他们两个,反倒成了stack和queue的底层容器

对于deque来说,它是一个指针数组,这些指针指向一片片空间,它并不真正的连续,而是由这些空间拼接而成

 从上面我们可知道的是,如果我们在中间插入数据,那么就会面临两种选择

1.扩容,这样减少了挪动数据,但是使得每个数组的空间大小不一样,这里数组空间的大小不一样就会导致我们在查找数据的时候需要一个个访问,这样就导致效率变得很低

2.挪动数据,也就是保持数组长度的不变,但是这样挪动的数据就太多了,也会导致效率变低,但是好处就是我们在访问元素的时候,效率会很高,假设每个数组都是10,我们要访问第i个元素,那么我先用i-=第一层的元素个数,然后x=i/10,这里的x就是第几层(层数从0开始),然后y=i%10这里的y是第几个,这样就可以快速的定位了

因为deque有这些缺点,所以也不能替代vector和list

总结

以上就是全部内容了,希望喜欢,多多支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1806110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何 Logrus IT 的质量评估门户帮助提升在线商店前端(案例研究)

在当今竞争激烈的电子商务环境中&#xff0c;一个运作良好的在线店面对商业成功至关重要。然而&#xff0c;确保目标受众获得积极的用户体验可能是一项挑战&#xff0c;尤其是在使用多种语言和平台时。Logrus IT的质量评估门户是一个强大的工具&#xff0c;可帮助企业简化内容和…

LLVM Cpu0 新后端3

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

MacOS中Latex提示没有相关字体怎么办

在使用mactex编译中文的时候&#xff0c;遇到有些中文字体识别不到的情况&#xff0c;例如遇到识别不到Songti.ttc。其实这个时候字体是在系统里面的&#xff0c;但是只不过是latex没有找到正确的字体路径。 本文只针对于系统已经安装了字体库并且能够用find命令搜到&#xff0…

Effective Java 1 用静态工厂方法代替构造器

知识点上本书需要会Java语法和lang、util、io库&#xff0c;涉及concurrent和function包。 内容上主要和设计模式相关&#xff0c;代码风格力求清晰简洁&#xff0c;代码尽量复用&#xff0c;组件尽量少依赖&#xff0c;错误尽早发现。 第1个经验法则&#xff1a;用静态工厂方…

rust学习(字节数组转string)

最新在写数据传输相关的操作&#xff0c;发现string一个有趣的现象&#xff0c;代码如下&#xff1a; fn main() {let mut data:[u8;32] [0;32];data[0] a as u8;let my_str1 String::from_utf8_lossy(&data);let my_str my_str1.trim();println!("my_str len is…

基于JSP技术的社区生活超市管理系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse开发环境、Tomcat服务器 系统展示 首页 管理员功能模块…

两款 IntelliJ IDEA 的 AI 编程插件

介绍两款 IntelliJ IDEA 的 AI 编程插件&#xff1a;通义灵码和 CodeGeeX。 通义灵码 这是由阿里推出的一个基于通义大模型的 AI 编码助手。 它提供了代码智能生成、研发智能问答等功能。通义灵码经过海量优秀开源代码数据训练&#xff0c;可以根据当前代码文件及跨文件的上下…

Spring运维之boo项目表现层测试匹配响应执行状态响应体JSON和响应头

匹配响应执行状态 我们创建了测试环境 而且发送了虚拟的请求 我们接下来要进行验证 验证请求和预期值是否匹配 MVC结果匹配器 匹配上了 匹配失败 package com.example.demo;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Auto…

Golang | Leetcode Golang题解之第129题求根节点到叶节点数字之和

题目&#xff1a; 题解&#xff1a; type pair struct {node *TreeNodenum int }func sumNumbers(root *TreeNode) (sum int) {if root nil {return}queue : []pair{{root, root.Val}}for len(queue) > 0 {p : queue[0]queue queue[1:]left, right, num : p.node.Left, …

C++使用thread_local实现每个线程下的单例

对于一个类&#xff0c;想要在每个线程种有且只有一个实例对象&#xff0c;且线程之间不共享该实例&#xff0c;可以按照单例模式的写法&#xff0c;同时使用C11提供的thread_local关键字实现。 在单例模式的基础上&#xff0c;使用thread_local关键字修饰单例的instance&…

离散数学答疑 4

知识点&#xff1a;什么是可结合&#xff1f; 举例A选项&#xff1a; 知识点&#xff1a;可交换性? 知识点&#xff1a;什么是阿贝尔群&#xff1f; 可交换->运算表中的元素关于主对角线对称 二阶子群的表达式 二阶子群作为一个群的子群&#xff0c;其本质是一个包含单位元…

【深度学习】温故而知新4-手写体识别-多层感知机+CNN网络-完整代码-可运行

多层感知机版本 import torch import torch.nn as nn import numpy as np import torch.utils from torch.utils.data import DataLoader, Dataset import torchvision from torchvision import transforms import matplotlib.pyplot as plt import matplotlib import os # 前…

SpringBoot+Vue学科竞赛系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 学生教师管理员 功能截图

IO流字符流(FileReader与FileWriter)

目录 FileReader 空参read方法 带参read方法&#x1f447; FileWriter void write(intc) 写出一个字符 void write(string str) 写出一个字符串 void write(string str,int off,int len) 写出一个字符串的一部分 void write(char[] cbuf) …

如何学习Golang语言!

第一部分&#xff1a;Go语言概述 起源与设计哲学&#xff1a;Go语言由Robert Griesemer、Rob Pike和Ken Thompson三位Google工程师设计&#xff0c;旨在解决现代编程中的一些常见问题&#xff0c;如编译速度、运行效率和并发编程。主要特点&#xff1a;Go语言的语法简单、编译…

Bootstrap框架集成ECharts教程

最新公司项目要在原有的基础上增加一些饼状图和柱状图来统计一些数据给客户&#xff0c;下面就是集成的一个过程&#xff0c;还是很简单的&#xff0c;分为以下几步 1、引入ECharts的包 2、通过ECharts官网或者菜鸟教程直接拿示例代码过来修修改改直接用就可以了 注意&#xf…

三维地图Cesium,加载一个模型,模型沿着给定的一组经纬度路线移动

目录 实现效果 实现思路 功能点 选择移动路线 加载模型和移动路线 重新运行 指定位置(经纬度点)开始移动 视角切换 到站提示 运行 停止 联动接口 完整代码 html js逻辑 trainOperation.js sourceData.js gitee仓库项目代码 疑问解答 实现效果 三维地图Cesiu…

pyqt QlineEdit内部增加按钮方法

按钮放在QlineEdit内部&#xff0c;界面更紧凑&#xff0c;体现了按钮和文本框的强关联。 def addButton(self,lineEdit):btn QtWidgets.QPushButton("")icon1 QtGui.QIcon()icon1.addPixmap(QtGui.QPixmap(":/image/images/th.png"), QtGui.QIcon.Norm…

C++【STL】改造红黑树简单模拟实现set map(带你了解set map的底层实现结构)

目录 一、学前铺垫&#xff08;泛型编程&#xff09; 二、改造红黑树 1.红黑树节点的改造 2.insert的改造 3.迭代器的实现 4.完整改造代码 三、set的模拟实现封装 四、map的模拟实现封装 五、完结撒❀ 前言&#xff1a; 下面为了简单模拟实现set map所出现的代码是以…

【JsDoc】JsDoc用法 | 巧妙用法

type type {other} other 接收表达式或字符 1、数组代码提示 1、效果图 1、码 /*** type {Array.<play|paush|next>} */ let music []2、字符串提示 2、效果图 2、码 /*** type {a|b|c}*/ let str