文章目录
- 前言
- 一、栈是什么?
- 逆波兰表达式(RPN)
- 二、队列是什么?
- BFS搜索
- 总结
前言
C++ 是一种面向对象的编程语言,它提供了多种数据结构,前面文章已介绍过数组、链表、hash表,并用自己的方法实现。用于存储和操作数据的结构在STL中还有很多,其中两种常用的数据结构是栈和队列。本文将介绍栈和队列的概念,特点,实现方式和应用场景。
一、栈是什么?
栈是一种后进先出(LIFO)的数据结构,它只允许在一端(称为栈顶)进行插入和删除操作。栈可以用数组或链表来实现。C++ 标准库中提供了一个模板类 std::stack,可以方便地创建和使用栈对象。栈的基本操作有:
- push(x):将元素 x 压入栈顶。
- pop():弹出并返回栈顶元素。
- top():返回栈顶元素,但不弹出。
- empty():判断栈是否为空。
- size():返回栈中元素的个数。
栈的应用场景有:
- 函数调用:当一个函数被调用时,它的参数,局部变量和返回地址都会被压入系统栈中,当函数返回时,它们会被弹出。这样可以实现函数的嵌套调用和递归调用。
- 表达式求值:当遇到一个算术或逻辑表达式时,可以用一个操作数栈和一个操作符栈来辅助求值。遇到操作数时,压入操作数栈;遇到操作符时,如果操作符栈为空或者当前操作符的优先级高于栈顶操作符的优先级,则压入操作符栈;否则,弹出操作符栈的栈顶元素,并从操作数栈中弹出两个元素,进行相应的计算,并将结果压入操作数栈,重复此过程直到遇到表达式结束符或操作符栈为空,最后从操作数栈中弹出最终结果。
- 括号匹配:当遇到一个包含括号的字符串时,可以用一个栈来检查括号是否匹配。遇到左括号时,压入栈;遇到右括号时,如果栈为空或者栈顶元素与右括号不匹配,则说明括号不匹配;否则,弹出栈顶元素,重复此过程直到字符串结束或者栈为空,最后如果栈为空,则说明括号匹配。
逆波兰表达式(RPN)
例如每日一练中的逆波兰表达式(RPN)。RPN是一种不需要括号的数学表达式,它使用操作符后置的方式来表示运算顺序。例如,表达式 2 + 3 * 4 可以写成 2 3 4 * +。为了计算RPN表达式,我们可以使用一个栈来存储操作数和操作符。每当遇到一个操作数,我们就把它压入栈中;每当遇到一个操作符,我们就从栈中弹出两个操作数,进行相应的运算,并把结果压入栈中。最后,栈中剩下的元素就是表达式的值:
#include <iostream>
#include <string>
#include <stack>
#include <sstream>using namespace std;
int solution(int n, std::string arr){int result;// TODO:stringstream ss(arr);stack<int> s; //创建一个空栈string input; //输入的字符串while (ss >> input){if (input == "+"){ //如果输入是加号int a = s.top(); s.pop(); //弹出栈顶操作数int b = s.top(); s.pop(); s.push(a + b); //把两个操作数相加的结果压入栈中} else if (input == "-"){ //如果输入是减号int a = s.top(); s.pop(); int b = s.top(); s.pop(); s.push(b - a); } else if (input == "*"){ //如果输入是乘号int a = s.top(); s.pop(); int b = s.top(); s.pop(); s.push(a * b); } else if (input == "/"){ //如果输入是除号int a = s.top(); s.pop(); int b = s.top(); s.pop(); s.push(b / a); } else { //如果输入是其他字符,认为是一个整数s.push(std::stoi(input)); //把输入转换成整数并压入栈中}}result = s.top(); //输出最终结果return result;
}int main() {int n;std::string arr = "3 4 - 5 +";int result = solution(n, arr);std::cout<<result<<std::endl;return 0;
}
二、队列是什么?
队列是一种先进先出(FIFO)的数据结构,它只允许在一端(称为队尾)进行插入操作,在另一端(称为队首)进行删除操作。队列可以用数组或链表来实现。C++ 标准库中提供了一个模板类 std::queue,可以方便地创建和使用队列对象。队列的基本操作有:
- push(x):将元素 x 插入队尾。
- pop():弹出并返回队首元素。
- front():返回队首元素,但不弹出。
- back():返回队尾元素,但不插入。
- empty():判断队列是否为空。
- size():返回队列中元素的个数。
队列的应用场景有:
- 缓冲区:当有多个生产者和消费者同时对同一资源进行访问时,可以用一个队列来作为缓冲区,保证生产者和消费者之间的同步和互斥。生产者将生产的数据插入队尾,消费者从队首取出数据进行消费。
- 广度优先搜索:当需要对一个图或者树进行遍历时,可以用一个队列来辅助实现广度优先搜索(BFS)。首先将起始节点插入队列,然后循环执行以下步骤:从队首取出一个节点,并访问它;将该节点的所有未访问过的邻居节点插入队尾;重复此过程直到队列为空或者找到目标节点。
- 线程池:当有多个任务需要执行时,可以用一个队列来存储任务,并创建一定数量的线程来执行任务。每个线程从队首取出一个任务,并执行它;如果任务执行完毕,则再从队首取出下一个任务;如果任务执行过程中发生异常,则将任务重新插入队尾;重复此过程直到队列为空或者线程终止。
BFS搜索
下面是一个使用C++和队列(queue)实现的简单BFS搜索例子:
#include <iostream>
#include <queue>
using namespace std;int main(){queue<int> q;int graph[4][4] = {{0, 1, 1, 0},{0, 0, 1, 0},{1, 0, 0, 1},{0, 0, 0, 1}};bool visited[4] = {false};q.push(0);visited[0] = true;while (!q.empty()){int v = q.front();q.pop();cout << v << " ";for (int i = 0; i < 4; i++){if (graph[v][i] == 1 && !visited[i]){q.push(i);visited[i] = true;}}}return 0;
}
上面的代码中,我们定义了一个二维数组graph来表示图。graph[i][j]为1表示顶点i和顶点j之间有一条边。我们还定义了一个布尔数组visited来记录每个顶点是否已经被访问过。
在函数中,我们首先将起始顶点(顶点0)加入队列,并将其标记为已访问。然后,我们进入一个循环,直到队列为空。在每次循环中,我们从队列的前端取出一个顶点v,并输出它。接着,我们遍历与顶点v相邻的所有顶点,如果这些顶点尚未被访问过,则将它们加入队列,并将它们标记为已访问。
总结
总之,C++ 栈和队列是两种常用的数据结构,它们有各自的特点和适用场景。掌握它们的概念,特点,实现方式和应用场景,对于提高编程能力和解决实际问题都有很大帮助。
栈和队列的实现比较容易,用数组就能模拟一个,这里就简单的介绍了一下如何灵活使用。至此就写完了常用的C++内置数据结构了,以后再写树和图。