什么是栈
栈(stack)是一种数据结构,它是一种具有特定方法的线性表,又称之为访问受限的线性表,它是许多表达式,符号等运算的基础,也是递归的底层实现。。栈的特点是先进后出(FILO,First In Last Out),即最后进入的元素最先出来,而最先进入的元素最后出来。栈通常有两种基本操作:压栈(push),在栈顶插入元素;出栈(pop),删除栈顶元素。
栈在计算机科学中有广泛的应用,比如函数调用的时候会使用栈来保存函数的调用顺序和局部变量;浏览器的访问历史可以使用栈来实现页面的后退和前进;编译器和解释器中使用栈来存储临时数据和函数调用栈;操作系统中也会使用栈来存储程序执行的上下文等。 栈的实现方式有多种,比如可以使用数组或链表来实现。栈的重要性不言而喻,它为解决许多实际问题提供了便利的途径。
栈的结构如下:
栈的操作
push():增加一个元素
pop():弹出一个元素
peek():显示栈顶元素,但是不出栈
empty():判断栈是否为空
栈的实现
栈的实现可以用数组,也可以用链表,或者使用Java的LinkedList三种方式
用数组实现
#include <iostream>
#include <stack>
int main() {
std::stack<int> myStack;
// 压入元素
myStack.push(1);
myStack.push(2);
myStack.push(3);
// 访问栈顶元素并出栈
std::cout << "栈顶元素:" << myStack.top() << std::endl;
myStack.pop();
// 输出栈中元素数量
std::cout << "栈中元素数量:" << myStack.size() << std::endl;
// 判断栈是否为空
if(myStack.empty()) {
std::cout << "栈为空" << std::endl;
} else {
std::cout << "栈不为空" << std::endl;
}
return 0;
}
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> myStack = new Stack<>();
// 压入元素
myStack.push(1);
myStack.push(2);
myStack.push(3);
// 访问栈顶元素并出栈
System.out.println("栈顶元素:" + myStack.peek());
myStack.pop();
// 输出栈中元素数量
System.out.println("栈中元素数量:" + myStack.size());
// 判断栈是否为空
if(myStack.isEmpty()) {
System.out.println("栈为空");
} else {
System.out.println("栈不为空");
}
}
}
用链表实现
用链表实现栈时,将插入与删除都在表头操作
#include <iostream>
// 定义链表节点结构体
struct Node {
int data;
Node* next;
};
class Stack {
private:
Node* top;
public:
// 构造函数,初始化栈顶指针为nullptr
Stack() : top(nullptr) {}
// 判断栈是否为空
bool isEmpty() {
return top == nullptr;
}
// 压栈
void push(int value) {
Node* newNode = new Node;
newNode->data = value;
newNode->next = top;
top = newNode;
}
// 出栈
void pop() {
if (isEmpty()) {
std::cout << "栈为空,无法出栈" << std::endl;
return;
}
Node* temp = top;
top = top->next;
delete temp;
}
// 获取栈顶元素
int topValue() {
if (isEmpty()) {
std::cout << "栈为空,栈顶元素不存在" << std::endl;
return -1;
}
return top->data;
}
// 析构函数,释放所有节点内存
~Stack() {
while (top != nullptr) {
Node* temp = top;
top = top->next;
delete temp;
}
}
};
int main() {
Stack myStack;
myStack.push(1);
myStack.push(2);
myStack.push(3);
std::cout << "栈顶元素:" << myStack.topValue() << std::endl;
myStack.pop();
std::cout << "栈顶元素:" << myStack.topValue() << std::endl;
myStack.pop();
std::cout << "栈顶元素:" << myStack.topValue() << std::endl;
myStack.pop();
return 0;
}
import java.util.EmptyStackException;
class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
class Stack {
private Node top;
public Stack() {
top = null;
}
public boolean isEmpty() {
return top == null;
}
public void push(int data) {
Node newNode = new Node(data);
newNode.next = top;
top = newNode;
}
public int pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
int data = top.data;
top = top.next;
return data;
}
public int peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return top.data;
}
}
public class Main {
public static void main (String[] args) {
Stack myStack = new Stack();
myStack.push(1);
myStack.push(2);
myStack.push(3);
System.out.println("栈顶元素:" + myStack.peek());
myStack.pop();
System.out.println("栈顶元素:" + myStack.peek());
myStack.pop();
System.out.println("栈顶元素:" + myStack.peek());
myStack.pop();
}
}
栈的经典算法
括号匹配问题
当遇到左括号时,将其入栈;当遇到右括号时,检查栈顶元素是否与其匹配的左括号,如果匹配则将栈顶元素出栈,继续遍历;如果不匹配或者栈为空,则说明括号不匹配,返回 false。最终,若字符遍历结束时,栈为空,则说明括号完全匹配,返回 true。
#include <iostream>
#include <stack>
#include <string>
bool isMatching(const std::string& str) {
std::stack<char> st;
for (char c : str) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (st.empty()) {
return false;
} else if ((c == ')' && st.top() == '(') || (c == ']' && st.top() == '[') || (c == '}' && st.top() == '{')) {
st.pop();
} else {
return false;
}
}
}
return st.empty();
}
int main() {
std::string str = "{[()]}";
if (isMatching(str)) {
std::cout << "Parentheses are matching." << std::endl;
} else {
std::cout << "Parentheses are not matching." << std::endl;
}
return 0;
}
最小栈
最小栈问题是一个常见的面试问题,通常要求设计一个特殊的栈数据结构,除了支持常规的栈操作(入栈、出栈、获取栈顶元素)之外,还要支持获取栈中的最小元素操作。即,设计一个栈,在常数时间复杂度内可以获取当前栈中的最小元素值。
#include <stack>
#include <iostream>
class MinStack {
private:
std::stack<int> dataStack;
std::stack<int> minStack;
public:
void push(int x) {
dataStack.push(x);
if (minStack.empty() || x <= minStack.top()) {
minStack.push(x);
}
}
void pop() {
if (dataStack.top() == minStack.top()) {
minStack.pop();
}
dataStack.pop();
}
int top() {
return dataStack.top();
}
int getMin() {
return minStack.top();
}
};
int main() {
MinStack minStack;
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
std::cout << "Current min element in stack: " << minStack.getMin() << std::endl; // Output: -3
minStack.pop();
std::cout << "Top element in stack: " << minStack.top() << std::endl; // Output: 0
std::cout << "Current min element in stack: " << minStack.getMin() << std::endl; // Output: -2
return 0;
}
在最小栈的实现中,dataStack 和 minStack 通常被用于存储不同类型的数据,并且它们之间具有一定的关系:
`dataStack`:数据栈用于存储传入的用户数据,即用户对栈进行 push 和 pop 操作时真正操作的栈。数据栈中的元素顺序代表用户数据的入栈和出栈顺序。
`minStack`:最小值栈(辅助栈)用于存储当前数据栈中的最小元素。当用户向数据栈压入元素时,如果该元素比当前最小元素小或等于当前最小元素,则要将该元素同时压入最小值栈。最小值栈中的栈顶元素始终代表当前数据栈中的最小元素。
这种设计的关键在于,在常数时间内获取当前栈中的最小元素。通过在每次 push 操作中维护最小值栈的栈顶元素,最小值栈可以保持与数据栈相同的压入和弹出顺序,并在 O(1) 的时间内返回当前栈中的最小元素值。 因此,dataStack 和 minStack 是两个栈,互相独立,但在实现中具有密切的关系,最小值栈的设计是为了支持在常数时间内获取最小元素的特殊需求。
最大栈
与最小栈相似
#include <stack>
#include <iostream>
class MaxStack {
private:
std::stack<int> dataStack;
std::stack<int> maxStack;
public:
void push(int x) {
dataStack.push(x);
if (maxStack.empty() || x >= maxStack.top()) {
maxStack.push(x);
}
}
void pop() {
if (dataStack.top() == maxStack.top()) {
maxStack.pop();
}
dataStack.pop();
}
int top() {
return dataStack.top();
}
int getMax() {
return maxStack.top();
}
};
int main() {
MaxStack maxStack;
maxStack.push(2);
maxStack.push(5);
maxStack.push(1);
std::cout << "Current max element in stack: " << maxStack.getMax() << std::endl; // Output: 5
maxStack.pop();
std::cout << "Top element in stack: " << maxStack.top() << std::endl; // Output: 5
std::cout << "Current max element in stack: " << maxStack.getMax() << std::endl; // Output: 2
return 0;
}
计算器问题
计算器问题通常是指实现一个简单的计算器程序,该程序可以对包含加减乘除以及括号的数学表达式进行求值。该问题的解决涉及到处理运算符优先级、括号的计算顺序以及实现逆波兰表达式等方法。解决计算器问题的最好方法就是栈。
#include <iostream>
#include <stack>
#include <string>
int calculate(std::string s) {
std::stack<int> nums;
std::stack<char> ops;
int num = 0;
char sign = '+';
for (int i = 0; i < s.length(); i++) {
char c = s[i];
if (isdigit(c)) {
num = num * 10 + (c - '0');
}
if ((!isdigit(c) && c != ' ') || i == s.length() - 1) {
if (sign == '+') {
nums.push(num);
} else if (sign == '-') {
nums.push(-num);
} else if (sign == '*') {
int prev = nums.top();
nums.pop();
nums.push(prev * num);
} else if (sign == '/') {
int prev = nums.top();
nums.pop();
nums.push(prev / num);
}
num = 0;
sign = c;
}
}
int result = 0;
while (!nums.empty()) {
result += nums.top();
nums.pop();
}
return result;
}
int main() {
// Example usage
std::string s = "3+2*2";
int result = calculate(s);
std::cout << "Result: " << result << std::endl; // Output: 7
return 0;
}