数据结构和算法(4):栈与队列

栈 ADT 及实现

栈(stack)是存放数据对象的一种特殊容器,其中的数据元素按线性的逻辑次序排列,故也可定义首、末元素。
尽管栈结构也支持对象的插入和删除操作,但其操作的范围仅限于栈的某一特定端。
也就是说,若约定新的元素只能从某一端插入其中,则反过来也只能从这一端删除已有的元素。禁止操作的另一端,称作盲端。

后进先出:从栈结构的整个生命期来看,更晚(早)出栈的元素,应为更早(晚)入栈者。

ADT功能
size()返回栈的规模
empty()判断栈是否为空
push(e)将 e 插至栈顶
pop()删除栈顶对象
top()引用栈顶对象

实现:

#include "../Vector/Vector.h" //以向量为基类,派生出栈模板类
template <typename T> class Stack: public Vector<T> { //将向量首/末端作为栈底/顶
public: //size()、empty()以及其它开放接口,均可直接沿用void push ( T const& e ) { insert ( size(), e ); } //入栈:等效于将新元素作为向量末元素插入T pop() { return remove ( size() - 1 ); } //出栈:等效于删除向量末元素T& top() { return ( *this ) [size() - 1]; } //取顶:直接返向量末元素
};

栈与递归

在 Windows 等 大部分操作系统中,每个运行中的二进制程序都配有一个调用栈(call stack)或执行栈(execution stack)。
借助调用栈可以跟踪属于同一程序的所有函数,记录它们之间的相互调用关系,并保证在每一调用实例执行完毕之后,可以准确地返回。

调用栈的基本单位是帧(frame)
每次函数调用时,都会相应地创建一帧,记录该函数实例在二进制程序中的返回地址,以及局部变量、传入参数等,并将该帧压入调用栈。若在该函数返回之前又发生新的调用,则同样地要将与新函数对应的一帧压入栈中,成为新的栈顶。函数一旦运行完毕,对应的帧随即弹出,运行控制权将被交还给该函数的上层调用函数,并按照该帧中记录的返回地址确定在二进制程序中继续执行的位置。
在任一时刻,调用栈中的各帧,依次对应于那些尚未返回的调用实例,亦即当时的活跃函数实例。特别地,位于栈底的那帧必然对应于入口主函数main(),若它从调用栈中弹出,则意味着整个程序的运行结束,此后控制权将交还给操作系统.

进制转换

进制算法流程:
十进制转二进制: 使用除2取余法,从十进制数中反复除以2,将余数记录下来,然后将余数从下到上排列起来。
二进制转十进制: 从二进制的最右边开始,每个位上的数字乘以2的幂,然后将结果相加。

void convert ( Stack<char>& S,_int64 n, int base ) { //十进制数n到base进制的转换(迭代版)static char digit[] //0 <n, 1 < base <m 16,新进制下的数位符号,可视base取值范围适当扩充= { '0''1''2''3''4' , '5', '6''7''8', '9''A''B''C''D''E''F');while ( n > e ) { //由低到高,逐一计算出新进制下的各数位int remainder = ( int ) ( n % base ); S.push(digit[remainder] );	//余数(当前位)入栈n/= base; //n 更新为其对 base 的除商}
}//新进制下由高到低的各数位,自顶而下保存于栈s中main(){Stack<char> S; convert(S, n, base);	//用栈记录转换得到的各数位while(!S.empty())printf("%c",S.pop());	//逆序输出
}

括号匹配

括号匹配的任务是,对任一程序块,判断其中的括号是否在嵌套的意义下完全匹配(简称匹配)。
顺序扫描表达式,用栈记录已扫描的部分:凡遇 (,则进栈;凡遇 ),则出栈。

#include <iostream>
#include <stack>
#include <string>bool isBracketMatch(const std::string& input) {std::stack<char> brackets;for (char c : input) {if (c == '(' || c == '{' || c == '[') {brackets.push(c);} else if (c == ')' || c == '}' || c == ']') {if (brackets.empty()) {return false; // 括号不匹配,没有左括号与右括号匹配}char top = brackets.top();brackets.pop();if ((c == ')' && top != '(') || (c == '}' && top != '{') || (c == ']' && top != '[')) {return false; // 括号不匹配}}}return brackets.empty(); // 所有括号都正确匹配
}int main() {std::string input = "{[()]}";if (isBracketMatch(input)) {std::cout << "Brackets are matched." << std::endl;} else {std::cout << "Brackets are not matched." << std::endl;}return 0;
}

在只有一种括号类型时,可以使用计数器起到与栈相同的效果;但存在多种括号类型时,计数器无法使用。

栈混洗

栈混洗是一个经典的问题,涉及到两个栈,其中一个栈包含一组数字,需要确定是否可以通过一系列栈操作将这些数字从一个初始顺序重新排列成另一个目标顺序。

问题的形式如下:

给定两个整数数组,一个代表初始栈的顺序,另一个代表目标栈的顺序,判断是否可以通过以下栈操作将初始栈的元素重新排列成目标栈的顺序:
1.将元素从初始栈的顶部移动到输出栈(可以看作是出栈操作)。
2.将元素从输入栈的底部移动到输出栈(可以看作是将输入栈反转后的出栈操作)。

如果可以,返回 true,否则返回 false

解决这个问题的一种常见方法是使用模拟。可以创建一个辅助栈来模拟操作,并按照目标顺序进行操作。具体步骤如下:
1.初始化一个辅助栈。
2.遍历目标栈的顺序(从左到右):
3.如果目标栈的当前元素与初始栈的顶部元素相同,直接从初始栈弹出元素。
4.否则,从初始栈中弹出元素,并将其推入辅助栈,直到找到与目标栈当前元素相同的元素。
5.继续遍历目标栈,如果辅助栈的栈顶元素与目标栈的当前元素相同,则从辅助栈中弹出元素。
6.最后,如果初始栈为空并且辅助栈也为空,返回 true;否则返回 false

#include <iostream>
#include <stack>
#include <vector>bool isStackPermutation(const std::vector<int>& input, const std::vector<int>& target) {std::stack<int> initialStack;std::stack<int> auxStack;for (int num : input) {initialStack.push(num);}for (int num : target) {while (!initialStack.empty() && initialStack.top() != num) {auxStack.push(initialStack.top());initialStack.pop();}if (!initialStack.empty() && initialStack.top() == num) {initialStack.pop();} else if (!auxStack.empty() && auxStack.top() == num) {auxStack.pop();} else {return false;}}return initialStack.empty() && auxStack.empty();
}int main() {std::vector<int> input = {1, 2, 3};std::vector<int> target = {2, 1, 3};if (isStackPermutation(input, target)) {std::cout << "The permutation is valid." << std::endl;} else {std::cout << "The permutation is not valid." << std::endl;}return 0;
}

栈混洗计数为: ( 2 n ! ) ( n + 1 ) ! n ! = c a t a l a n ( n ) \frac{(2n!)}{(n+1)!n!} = catalan(n) (n+1)!n!(2n!)=catalan(n)

甄别栈混洗: B B B A A A 的一个栈混洗,当且仅当对于任意 1 ≤ i < j < k ≤ n 1 \leq i < j < k \leq n 1i<j<kn,P 中都不包含如下模式: { . . . , k , . . . , i , . . . , j , . . . } \{ ..., k, ..., i, ..., j, ...\} {...,k,...,i,...,j,...},例如{3,1,2}.

中缀表达式求值

思路:

将中缀表达式转换为后缀表达式:创建一个空栈,用于存储操作符。从左到右遍历中缀表达式中的每个字符(数字和操作符)。如果遇到数字,直接输出到输出队列。如果遇到操作符:如果栈为空,将操作符压入栈。否则,比较操作符与栈顶操作符的优先级:如果操作符优先级高于栈顶操作符,将操作符压入栈。否则,弹出栈中较高或相等优先级的操作符,并将它们输出到输出队列,直到遇到更低优先级的操作符或栈为空,然后将当前操作符压入栈。如果遇到左括号"(",将其压入栈。如果遇到右括号")",弹出栈中的操作符并将它们输出到输出队列,直到遇到左括号"(",然后将左括号从栈中弹出但不输出。遍历结束后,将栈中剩余的操作符全部输出到输出队列。计算后缀表达式的值:创建一个空栈,用于存储操作数。从左到右遍历后缀表达式中的每个元素(数字和操作符)。如果遇到数字,将其压入栈。如果遇到操作符,从栈中弹出所需数量的操作数(通常是两个),执行相应的运算,然后将结果压入栈。最终,栈中将只剩下一个元素,即表达式的值。

代码实现:

#include <iostream>
#include <stack>
#include <string>
#include <sstream>
#include <cctype>//返回操作符的优先级,优先级越高,越早进行计算
int precedence(char op) {if (op == '+' || op == '-') return 1;if (op == '*' || op == '/') return 2;return 0;
}
//于执行操作符的运算,根据不同的操作符执行不同的操作,并返回结果
double applyOperator(double operand1, double operand2, char op) {switch (op) {case '+': return operand1 + operand2;case '-': return operand1 - operand2;case '*': return operand1 * operand2;case '/': return operand1 / operand2;default: return 0.0; // 处理未知操作符}
}double evaluateInfixExpression(const std::string& expression) {std::stack<char> operators;std::stack<double> operands;//operators 用于存储操作符,operands 用于存储操作数std::istringstream iss(expression);//创建了一个 std::istringstream 对象,将中缀表达式字符串 expression 包装为输入流,并准备逐个读取字符串中的标记std::string token;while (iss >> token) {	//程序检查当前标记 token 是数字还是括号。如果是数字(包括正数和负数),将其转换为 double 类型并压入 operands 栈。如果是左括号 "(",将其压入 operators 栈。if (isdigit(token[0]) || (token.length() > 1 && token[0] == '-' && isdigit(token[1]))) {double operand = std::stod(token);operands.push(operand);}else if (token == "(") {operators.push('(');}else if (token == ")") {//在遇到右括号 ")" 时,程序将执行一系列操作来处理括号内的表达式。它会弹出操作符直到遇到左括号 "(",并对括号内的表达式进行计算,将结果压入 operands 栈。//如果在遇到左括号之前就遇到了栈空或其他操作符,表示右括号没有正确匹配,将返回0.0表示错误。while (!operators.empty() && operators.top() != '(') {char op = operators.top();operators.pop();if (operands.size() < 2) {// 处理错误:操作数不足return 0.0;}double operand2 = operands.top();operands.pop();double operand1 = operands.top();operands.pop();double result = applyOperator(operand1, operand2, op);operands.push(result);}if (!operators.empty() && operators.top() == '(') {operators.pop();}else {// 处理错误:未匹配的右括号return 0.0;}}else {while (!operators.empty() && precedence(operators.top()) >= precedence(token[0])) {char op = operators.top();operators.pop();if (operands.size() < 2) {// 处理错误:操作数不足return 0.0;}double operand2 = operands.top();operands.pop();double operand1 = operands.top();operands.pop();double result = applyOperator(operand1, operand2, op);operands.push(result);}operators.push(token[0]);}}while (!operators.empty()) {char op = operators.top();operators.pop();if (operands.size() < 2) {// 处理错误:操作数不足return 0.0;}double operand2 = operands.top();operands.pop();double operand1 = operands.top();operands.pop();double result = applyOperator(operand1, operand2, op);operands.push(result);}if (operands.size() != 1 || !operators.empty()) {// 处理错误:操作数和操作符未匹配return 0.0;}return operands.top();
}int main() {std::string infixExpression = "2 + 3 * 4 - 1";double result = evaluateInfixExpression(infixExpression);if (result != 0.0) {std::cout << "Result: " << result << std::endl;}else {std::cout << "Invalid expression." << std::endl;}return 0;
}

当前的操作符比栈顶的操作符优先级低时,进行实际的运算。

逆波兰表达式

逆波兰表达式(Reverse Polish Notation,RPN),也称为后缀表达式,是一种数学表达式表示法,其中操作符在操作数之后。这种表示法消除了括号,并且使得表达式的计算顺序更加明确,不需要考虑操作符的优先级。

手工转换
假设:事先未就运算符之间的优先级关系做出过任何约定
1)用括号显式地表示优先级
2)将运算符移到对应的右括号后
3)抹去所有括号,整理。

//原式
( 0 ! + 1 ) * 2 ^ ( 3 ! + 4 ) - ( 5 ! - 67 - ( 8 + 9 ) )
//增添足够多的括号
( ( ( ( 0 ) ! + 1 ) * ( 2 ^ ( ( 3 ) ! + 4 ) ) ) - ( ( ( 5 ) ! - 67 ) - ( 8 + 9 ) ) )
//各运算符后移,使之紧邻于其对应的右括号的右侧:
( ( ( ( 0 ) ! 1 ) + ( 2 ( ( 3 ) ! 4 ) + ) ^ ) * ( ( ( 5 ) ! 67 ) - ( 8 9 ) + ) - ) -
//最后抹去所有括号:
0 ! 1 + 2 3 ! 4 + ^ * 5 ! 67 - 8 9 + - -

操作数之间的相对次序,在转换前后保持不变;而运算符在RPN中所处的位置,恰好就是其对应的操作数均已就绪且该运算可以执行的位置。

队列 ADT 及实现

队列像栈一样,也是受限的序列:
只能在队尾插入(查询):enqueue() + rear()
只能在队头删除(查询):dequeue() + front()
先进先出,后进后出。

队列既然属于序列的特列,故亦可直接基于向量或列表派生。

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

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

相关文章

持安科技入选数说安全《2023中国网络安全市场年度报告》

近日&#xff0c;网络安全产业研究平台数说安全发布《2023中国网络安全市场年度报告》&#xff0c;报告共分为158页核心报告&#xff0c;及番外篇《网安融资新星及融资过亿企业介绍》&#xff0c;作为以甲方身份创业的零信任办公安全明星企业&#xff0c;持安科技以网安融资新星…

MATLAB R2023a完美激活版(附激活补丁)

MATLAB R2023a是一款面向科学和工程领域的高级数学计算和数据分析软件&#xff0c;它为Mac用户提供了强大的工具和功能&#xff0c;用于解决各种复杂的数学和科学问题。以下是MATLAB R2023a Mac的一些主要特点和功能&#xff1a; 软件下载&#xff1a;MATLAB R2023a完美激活版 …

select多选回显问题 (取巧~)

要实现的效果&#xff1a; 实际上select选择框&#xff0c;我想要的是数组对象&#xff0c;但是后端返回来的是个字符串。 以下是解决方法&#xff1a; 以上是一种简单的解决方法~ 也可以自己处理数据或者让后端直接改成想要的格式。

Kafka3.0.0版本——消费者(手动提交offset)

目录 一、消费者&#xff08;手动提交 offset&#xff09;的概述1.1、手动提交offset的两种方式1.2、手动提交offset两种方式的区别1.3、手动提交offset的图解 二、消费者&#xff08;手动提交 offset&#xff09;的代码示例2.1、手动提交 offset&#xff08;采用同步提交的方式…

Python爬虫 教程:IP池的使用

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 一、简介 爬虫中为什么需要使用代理 一些网站会有相应的反爬虫措施&#xff0c;例如很多网站会检测某一段时间某个IP的访问次数&#xff0c;如果访问频率…

MySQL 笔记

目录 1. MySQL 笔记1.1. mwb 是什么文件 1. MySQL 笔记 1.1. mwb 是什么文件 MWB 文件 MWB 是 MySQL Workbench 的默认文件格式, 包含所有数据库的结构和数据。MWB 格式可以直接导入到 MySQL 中, 使得数据库的迁移变得更加简单。在 MySQL Workbench 中, 创建 MWB 文件可以通过…

[SICTF 2023 #Round2] Crypto,PWN,Reverse

似乎很久没写了。 周五到周日&#xff0c;两天的这个比赛&#xff0c;有些东西还真是头回用&#xff0c;值得纪录一下。 Crypto 密码这块这届还是比较简单的&#xff0c;没有复杂的题&#xff0c;但量大分多。 【签到】古典大杂烩 给了一堆emoji的图 &#x1f429;&#x…

GCP Architect之VPN+Network

VPN 搜索结果共计:11 [单选]As part of implementing their disaster recovery plan, your company is trying to replicate their production MySQL database from their private data center to their GCP project using a Google Cloud VPN connection. They are experien…

OpenCV(三十四):轮廓外接最大、最小矩形和多边形拟合

目录 1.轮廓外接最大矩形boundingRect() 2.轮廓外接最小矩形minAreaRect() 3.轮廓外接多边形approxPolyDP() 1.轮廓外接最大矩形boundingRect() Rect cv::boundingRect ( InputArray array ) array:输入的灰度图像或者2D点集&#xff0c;数据类型为vector<Point>或者M…

LA@二次型@标准化相关原理和方法

文章目录 标准化方法正交变换法&#x1f388;求矩阵的特征值求各特征值对应的线性无关特征向量组正交化各个向量组 配方法步骤例例 初等变换法原理总结初等变换法的步骤例 标准化方法 正交变换法&#x1f388; 二次型可标准化定理的证明过程给出使用二次型标准化的步骤 该方法…

Go语言的[GPM模型]

在go中,线程是运行Groutine的实体,调度器的功能是把可以运行的Groutine分配到工作线程上 GPM模型 M与P的数量没有绝对的数量关系,当一个M阻塞时,P就会创建一个或者切换到另一个M,所以即使设置了runtime.GOMAXPROCS(1) 也可能创建多个M出来; 当M发现给自己输送G协程的那个P队列为…

什么是脚本语言,解释脚本语言的特点和应用领域

1、什么是脚本语言&#xff0c;解释脚本语言的特点和应用领域。 脚本语言是一种编程语言&#xff0c;通常用于自动化任务或脚本。它们通常比传统的编程语言更容易学习和使用&#xff0c;因为它们通常具有更少的语法和更简单的命令。 脚本语言的特点包括&#xff1a; 简单易学…

《AI一键生成抖音商品种草文案》让你秒变带货王!

在这个数字化的时代&#xff0c;我们的生活被各种应用所包围&#xff0c;其中&#xff0c;抖音作为一款短视频分享平台&#xff0c;已经成为了我们生活中不可或缺的一部分。然而&#xff0c;作为一名抖音创作者&#xff0c;你是否曾经遇到过这样的困扰&#xff1a;在创作商品种…

HJ57 高精度整数加法

题目&#xff1a; HJ57 高精度整数加法 题解&#xff1a; 1.逐位相加 按照传统加减法模式&#xff0c;从最后一位开始&#xff0c;逐位相加&#xff0c;逢十进一&#xff0c;传统方式从右往左相加&#xff0c;可以将数字翻转&#xff0c;变成从左往右按照数组遍历顺序相加&…

C#程序到底从哪里开始看,从Main函数开始,那么Main函数是什么?

视觉人机器视觉粉丝问我,拿到自己公司得架构,问我,C#程序到底从哪里看,从Main函数开始,那么Main函数是什么? Main()函数 Main()是C#应用程序的入口点,执行这个函数就是执行应用程序。也就是说,在执行过程开始时,会执行Main()函数,在Main()函数执行完毕时,执行过…

【JavaSpring】spring接口-beanfactory和applicationcontext与事件解耦

beanfactory 1.applicationcontext的父接口 2.是Spring的核心容器 功能 表面只有getBean&#xff0c;但实现类默默发挥了巨大作用 1.管理所有bean 2.控制反转 3.基本的依赖注入 applicationcontext 功能 1.继承了MessageSource&#xff0c;有了处理国际化资源的能力 …

[H5动画制作系列] Sprite及Text Demo

参考代码: sprite.js: var canvas, stage, container; canvas document.getElementById("mainView"); function init() {stage new createjs.Stage(canvas);createjs.Touch.enable(stage);var loader new createjs.LoadQueue(false);loader.addEventListener(&q…

云计算与虚拟化

一、概念 什么是云计算&#xff1f; 云计算&#xff08;cloud computing&#xff09;是分布式计算的一种&#xff0c;指的是通过网络“云”将巨大的数据计算处理程序分解成无数个小程序&#xff0c;然后&#xff0c;通过多部服务器组成的系统进行处理和分析这些小程序得到结果…

SLAM论文详解(5) — Bundle_Adjustment_LM(BALM)论文详解

目录 1 摘要 2 相关工作 3 BA公式和导数 A. 直接BA公式 B. 导数 C. 二阶近似 4 自适应体素化 5. 将BALM结合进LOAM 6. 实验 7. 算法应用场景解析 1 摘要 Bundle Adjustment是一种用于同时估计三维结构和传感器运动运动的优化算法。在视觉SLAM&#xff0c;三维重建等…

剑指 Offer 30. 包含min函数的栈

剑指 Offer 30. 包含min函数的栈 方法一 使用两个栈。 class MinStack {Deque<Integer> stack new ArrayDeque<>();Deque<Integer> stack_min new ArrayDeque<>();public MinStack() {}public void push(int x) {stack.push(x);if(stack_min.isEm…