当前位置: 首页 > wzjs >正文

上海疫情饿死了多少人文章优化软件

上海疫情饿死了多少人,文章优化软件,做快手电商需要什么条件,宁海有做网站的吗目录 引言 一、容器适配器的本质 1.1 设计哲学 1.2 底层容器选择策略 二、Stack的工程实践 2.1 核心操作与内存模型 2.2 典型应用场景 场景1:括号匹配验证 场景2:函数调用栈模拟 练手算法题: 155. 最小栈 - 力扣(LeetC…

目录

引言

一、容器适配器的本质

1.1 设计哲学

1.2 底层容器选择策略

二、Stack的工程实践

2.1 核心操作与内存模型

2.2 典型应用场景

场景1:括号匹配验证

场景2:函数调用栈模拟

练手算法题:

155. 最小栈 - 力扣(LeetCode)

栈的压入、弹出序列_牛客题霸_牛客网

150. 逆波兰表达式求值 - 力扣(LeetCode) 后缀表达式

1. 快速判断运算符

2. 性能分析

224. 基本计算器 - 力扣(LeetCode)(给一个中缀计算结果)拓展题目

三、队列

3.1 创建队列对象

3.2 六大核心方法

队列的三大典型应用场景

3.3 消息队列实现

3.4 广度优先搜索(BFS)

3.5 实时数据缓冲

栈的模拟实现:

队列的模拟实现:

​编辑

deque实现思想:

deque优缺点


让我们先来看看库中的stack和queue

引言

在C++标准模板库(STL)中,stack和queue作为典型的容器适配器,是每位开发者必须掌握的核心数据结构。它们看似简单,却蕴含着程序设计中的重要哲学——通过限制操作来实现特定的数据管理策略。本文将从底层实现原理出发,结合工程实践中的典型应用场景,揭示这两种线性容器的本质特性。

一、容器适配器的本质

1.1 设计哲学

stack和queue并非独立容器,而是构建在其他序列容器(如deque、list)之上的适配器。这种设计体现了软件工程中的"组合优于继承"原则,通过限制基础容器的接口来实现特定的访问策略。

template <class T, class Container = deque<T>> 
class stack;template <class T, class Container = deque<T>>
class queue;

1.2 底层容器选择策略

  • stack默认使用deque:支持快速的首尾插入/删除操作(O(1)时间复杂度)

  • queue默认使用deque:同时需要高效的头部删除和尾部插入

  • 可替换容器类型验证:

    • vector(仅适用于stack)

    • list(适用于两者)

二、Stack的工程实践

2.1 核心操作与内存模型

stack<int> s;
s.push(1);      // 压栈 O(1)
s.emplace(2);   // 原地构造 C++11
s.top();        // 查看栈顶 O(1)
s.pop();        // 出栈 O(1)

内存增长示意图:

[栈底] -> 元素1 -> 元素2 -> ... -> 元素N [栈顶]

2.2 典型应用场景

场景1:括号匹配验证

解题思路是:

栈的使用:栈的“后进先出”特性完美匹配括号的嵌套关系。例如,遇到左括号时压栈,遇到右括号时检查栈顶是否匹配。

#include <stack>
using namespace std;bool isValidParentheses(const string& s) {stack<char> stk;for (char c : s) {if (c == ')' || c == ']' || c == '}') { // 当前字符是右括号if (stk.empty()) return false;     // 栈为空,无法匹配// 根据右括号类型检查栈顶是否匹配if ((c == ')' && stk.top() != '(') ||(c == ']' && stk.top() != '[') ||(c == '}' && stk.top() != '{')) {return false;}stk.pop(); // 匹配成功,弹出栈顶左括号} else {stk.push(c); // 左括号直接入栈}}return stk.empty();
}
场景2:函数调用栈模拟
struct FunctionCall {int returnAddress;vector<int> parameters;
};stack<FunctionCall> callStack;// 函数调用
callStack.push({0x0040A2B0, {1, 2, 3}});// 函数返回
FunctionCall ret = callStack.top();
callStack.pop();

练手算法题:

155. 最小栈 - 力扣(LeetCode)

解题思路:

提供两个栈,一个栈用来存原数_st,一个栈用来存小值_minst

_minst:在空栈的时候先和_st一样存入一个值,后续入栈时都需要与_minst栈顶比较,

入栈的值比当前_minst.top()值小或等于则继续入这个_minst栈。

代码实现:

class MinStack {
public:MinStack() {}void push(int val) {_st.push(val);if(_minst.empty() || _minst.top() >= val){_minst.push(val);}}void pop() {if(_minst.top() == _st.top()){_minst.pop();}_st.pop();}int top() {return _st.top();}int getMin() {return _minst.top();}stack<int> _st;stack<int> _minst;
};

栈的压入、弹出序列_牛客题霸_牛客网

解题思路:

1.先按入栈序列入栈

2.栈顶元素和出栈序列是否匹配

        a.如果匹配,则出数据,直到不匹配或栈为空

        b.如果不匹配,则继续入数据,直到匹配

3.结束标志:入栈序列走完了

代码实现:

    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {// write code hereint pushi = 0, popi = 0;stack<int> st;while(pushi < pushV.size()){st.push(pushV[pushi]);//st.push(pushV[pushi++]);下面的++pushi就不写了//栈不为空且栈顶元素和出栈序列匹配while(!st.empty()&&st.top() == popV[popi]){st.pop();++popi;}++pushi;}return st.empty();}

150. 逆波兰表达式求值 - 力扣(LeetCode) 后缀表达式

理解题意:

eg1:

中缀表达式:a + b * (c - d)

改成后缀表达式: abcd - * + 

eg2:

中缀表达式:a + b * c - d

后缀表达式:abc * + d - 

巧妙解法:操作数按顺序排列,再看中缀,每当遇见操作符就往前看操作数根据符号优先级能否直接运算,能,则填入运算符,不能则获取下一个操作数,再看前面已经有的操作数和已有的运算符能否计算。比如现将abcd写出,再看中缀,发现a + b不能直接运算,继续往下看,录入*,不能运算,再录入c ,b*c符合逻辑,写上a(bc*),前面的+,也可以运算,写上(a(bc*)+)继续往下看 ,只有-d,于是:(a(bc*)+)d-写出,去掉括号,abc*+d-

解题思路:

题目是给出后缀表达式,实际上我们可以按照就近(一个符号和两个值就可以组成一次运算,先取出来的值是右操作数,后取到的值是)倒推,比如说:

ab*c/d+ <--->((a*b)/c)+d <-->ab*c/d+

两个值加一个符号就能组成一次运算,得到一个运算结果,相当于一个新值,再获取一个值一个符号这样的顺序。

1.依次将对象中的字符入栈,遇到操作符就出栈,依次为右操作数和左操作数,再将运算结果入栈将成为下一个运算的左操作数。

2.最后一个在栈中得数就是总运算结果。

方法一:暴力解法

    class Solution {public:int evalRPN(vector<string>& tokens) {stack<int> s;for (size_t i = 0; i < tokens.size(); ++i){string& str = tokens[i];// str为数字if (!("+" == str || "-" == str || "*" == str || "/" == str)){s.push(atoi(str.c_str()));}else{// str为操作符int right = s.top();s.pop();int left = s.top();s.pop();switch (str[0]){case '+':s.push(left + right);break;case '-':s.push(left - right);break;case '*':s.push(left * right);break;case '/':// 题目说明了不存在除数为0的情况s.push(left / right);break;}}}return s.top();}};

 方法二:用set(底层是搜索树)来解 

1. 快速判断运算符

  • 核心目的:将运算符集合 (+-*/) 存储在一个 set 中,用于快速检查当前字符串是否为运算符。这里考虑到的是如果在符号很多的情况下,优先使用set能便于快速查找。

  • 实现方式

    
    if (s.find(str) != s.end()) {  // 判断 str 是否在集合 s 中// 执行运算符操作
    } else {// 处理操作数
    }
  • 2. 性能分析

  • 时间复杂度set 的查找操作 find() 的时间复杂度为 O(log n),其中 n 是集合大小(此处 n=4,实际几乎可以视为常数时间)。

  • 对比线性查找:对于少量元素(如4个运算符),set 的查找效率与逐个条件判断 (str == "+" || ...) 差异不大,但代码更简洁。

  • set 在这段代码中充当了一个运算符过滤器,通过预定义的运算符集合,高效区分当前 token 是操作符还是操作数。这种设计在代码简洁性可维护性之间取得了平衡,尤其适合需要灵活扩展运算符的场景。

代码实现:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;//initializer_list构造函数set<string> s = {"+","-","*","/"};for(auto str : tokens) {//1.操作数入栈,操作符运算if(s.find(str) != s.end()){//操作符int right = st.top();st.pop();int left = st.top();st.pop();switch(str[0])//case必须是整型(char也是整型){case '+':st.push(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;}}else{//没有找到操作符,就入栈st.push(stoi(str));//string转化为int类型}}return st.top();}
};

224. 基本计算器 - 力扣(LeetCode)(给一个中缀计算结果)拓展题目

解题思路:中缀转后缀

1.操作数输出

2.操作符入栈:

        a、栈为空,入栈

        b、比栈顶的运算符优先级高,就先入栈

        c、比栈顶的运算符优先级低,出栈顶运算符

        d、优先级相等,前一个可以先运算,出栈顶运算符

结束后将栈中操作符全部出栈。

转成后缀。

        只要遇到左括号就递归,遇到右括号结束。(递归时会建立新栈,新的一轮优先级的比较)。

代码:省略;

三、队列

3.1 创建队列对象

#include <queue>// 创建整型队列
queue<int> myQueue;// 使用其他容器作为底层实现
queue<string, list<string>> listBasedQueue;

3.2 六大核心方法

  1. 入队操作

myQueue.push(42);
myQueue.emplace("Hello"); // C++11高效构造
  1. 访问元素

cout << "队首元素: " << myQueue.front();
cout << "队尾元素: " << myQueue.back(); // 注意:非标准队列可能不支持
  1. 出队操作

myQueue.pop(); // 删除队首元素
  1. 容量查询

if (!myQueue.empty()) {cout << "当前元素数量: " << myQueue.size();
}

队列的三大典型应用场景

3.3 消息队列实现

class MessageQueue {
private:queue<string> messages;mutex mtx;public:void addMessage(const string& msg) {lock_guard<mutex> lock(mtx);messages.push(msg);}string getMessage() {lock_guard<mutex> lock(mtx);if (messages.empty()) return "";string msg = messages.front();messages.pop();return msg;}
};

应用场景:多线程通信、事件处理系统

3.4 广度优先搜索(BFS)

void BFS(vector<vector<int>>& graph, int start) {vector<bool> visited(graph.size(), false);queue<int> q;q.push(start);visited[start] = true;while (!q.empty()) {int current = q.front();q.pop();// 处理当前节点cout << "访问节点: " << current << endl;for (int neighbor : graph[current]) {if (!visited[neighbor]) {visited[neighbor] = true;q.push(neighbor);}}}
}

应用场景:社交网络关系分析、路径规划

3.5 实时数据缓冲

class DataBuffer {queue<SensorData> buffer;const int MAX_SIZE = 100;public:void addData(const SensorData& data) {if (buffer.size() >= MAX_SIZE) {buffer.pop(); // 移除最旧数据}buffer.push(data);}void processBatch() {while (!buffer.empty()) {process(buffer.front());buffer.pop();}}
};

应用场景:物联网传感器数据处理、音视频流缓冲

栈的模拟实现:

#pragma once
#include<vector>
#include<list>
namespace bit
{//写法一//template<class T>//class stack//{//public://private://	//1.数组//	T* _a;//	//2.top//	int _top;//	//3.容量//	int _capacity;//};// 写法二://设计模式:适配器模式  -- 转换//我们可以用vector、list来实现栈,因此也可以做一个容器模版,我们不会知道我们的栈是数组栈还是链表栈//泛型编程//stack<int, vector<int>> st1//stack<int, list<int>> st2template<class T, class Container>class stack{public://1 2 3 4 5void 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(){return _con.back();}private:Container _con;};

测试代码:

void test_stack1()
{bit::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;
}int main()
{test_stack1();
}

在我们实际应用时,stack的第二个参数是可以不传的,因此我们的模拟代码还需要修改。

我们的模版参数和函数参数相似,模版参数传的是类型,函数参数传的是对象,函数参数可以有缺省参数,模版参数也可以有缺省参数,从右往左缺省。

如图:写一个默认参数

队列的模拟实现:

和栈的模拟实现类似,有一些区别,因为队列是先进先出,因此在pop()时,应该pop_front(); 

以及在库中对于队列模版参数默认值是传的deque(双端队列),不是真队列(不要求先进先出)

deque是一个很牛的容器

vector

优点:支持下标随机访问

缺点:头部或者中间插入的效率低,扩容有消耗

list

优点:任意位置插入删除效率都不错

缺点:不支持下表随机访问

但如上图:我们可以发现,deque就是vector和list的合体

deque实现思想:

1.开多个小数组

2.中控指针数组 指针从中间往两边放,存小数组地址

尾差:最后一个buffer没满,就插入这个buffer里面,如果满了就新开一个buffer,在中控指针数组内再往后添加一个指针,指向新的数组地址

头插: 在中控指针数组内再往前添加一个指针指向新数组空间,在新的数组空间内头插(请注意:指针存的是数组地址,存入数据是从数组尾部开始存入)

中控数组满:扩容(类似于vector扩容的方式)

扩容后,要找第i个值需要做两个判断:

1.如果第一个buffer不满:   i -= 第一个buffer的数据个数。

(   如果是满的,就不用i -= 第一个buffer的数据个数。)

2.再算在第几个buffer里面:buff -> i/N;在这个buffer内的第几个? i%N

总图:

deque优缺点

优点:头插尾插的效率都很好。比顺序表和链表都要好一些

缺点:

1.中间插入删除会比较麻烦,效率一般。a.整体移动,b.局部移动

2.[]效率不够极致

为什么栈和队列的默认容器都是deque?因为deque的头尾插的效率很好。

队列的模拟实现代码:

#pragma once
#include<deque>
#include<list>
namespace bit
{template<class T, class Container = deque<T>>class Queue{public://1 2 3 4 5void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front(); //注意在队头出}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& front(){return _con.front();}const T& back(){return _con.back();}private:Container _con;};}

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

       在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注,这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

http://www.dtcms.com/wzjs/217740.html

相关文章:

  • 建设工程质量监督站网站百度一下京东
  • 如何自己做时时彩网站打开百度官网
  • 鲜花网站建设毕业论文招聘网络营销推广人员
  • 东营市做网站头条搜索是百度引擎吗
  • 运城有做网站设计百度授权代理商
  • 网站建设工具 公司网站友情链接检测
  • wordpress下载类型主题站长seo工具
  • 动画片制作教程东莞关键词seo
  • 运城建设银行网站江门网站建设模板
  • 管家网站百度视频排名优化
  • 一般通过男网友百度快照优化的优势是什么
  • seo整站优化+WordPress域名查询
  • 移动互联时代网站建设推广注册app赚钱平台
  • 网站开发用什么技术世界十大网站排名
  • 在线做文档的网站精准数据营销方案
  • 中信建设有限责任公司新区快速seo排名
  • 无锡响应式网站制作代发百度帖子包收录排名
  • 做课内教学网站百度投诉中心24小时电话
  • 成都动力无限网站推广网址怎么注册
  • 办公室装修设计怎么收费西安seo培训
  • 大连专业网站建设24小时免费看的视频哔哩哔哩
  • 青岛专门做网站的公司有哪些免费发布产品信息的网站
  • 吉林建设监理协会网站赣州seo外包怎么收费
  • 简洁大气的网站设计新乡网站优化公司价格
  • 宁德营销型网站建设腾讯域名
  • 中国建设监理业协会网站策划方案模板
  • 做网站填写主要品牌怎么填写郑州网站推广公司哪家好
  • 台州椒江网站建设百度seo排名优化公司哪家好
  • p2p金融网站建设蚌埠网络推广
  • 哪个网站能靠做软件卖网络营销推广方法和手段