数据结构中的栈(Stack)
栈是一种重要的线性数据结构,遵循"后进先出"(Last In, First Out, LIFO)的原则。这意味着最后一个进入栈的元素将是第一个被弹出的元素。栈通常用于深度优先搜索、逆波兰表达式计算、函数调用栈等场景。
栈的基本定义
栈的基本操作包括:
-
Push(压栈):将元素添加到栈的顶部。
-
Pop(出栈):从栈的顶部移除元素。
-
Peek(或 Top):查看栈顶的元素,但不移除它。
-
IsEmpty(判空):检查栈是否为空。
-
IsFull(判满):检查栈是否已满(如果栈的大小有限)。
栈的实现方式
栈可以通过数组或链表来实现。以下是两种实现方式的详细介绍:
1. 数组实现栈
数组实现的栈是一种静态栈,其大小在初始化时确定。数组实现栈的好处是简单易用,适合小规模数据处理。
示例代码(数组实现栈):
cpp
#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack *s) {
s->top = -1;
}
// 判断栈是否为空
bool isEmpty(Stack *s) {
return s->top == -1;
}
// 判断栈是否已满
bool isFull(Stack *s) {
return s->top == MAX_SIZE - 1;
}
// 压栈操作
void push(Stack *s, int value) {
if (isFull(s)) {
printf("栈已满,无法压栈\n");
return;
}
s->top++;
s->data[s->top] = value;
}
// 出栈操作
int pop(Stack *s) {
if (isEmpty(s)) {
printf("栈为空,无法出栈\n");
return -1; // 返回一个无效值表示错误
}
int value = s->data[s->top];
s->top--;
return value;
}
// 查看栈顶元素
int peek(Stack *s) {
if (isEmpty(s)) {
printf("栈为空,无法查看栈顶\n");
return -1; // 返回一个无效值表示错误
}
return s->data[s->top];
}
int main() {
Stack s;
initStack(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
printf("栈顶元素: %d\n", peek(&s));
printf("出栈元素: %d\n", pop(&s));
printf("栈顶元素: %d\n", peek(&s));
return 0;
}
输出:
cpp
栈顶元素: 30
出栈元素: 30
栈顶元素: 20
2. 链表实现栈
链表实现的栈是一种动态栈,其大小可以根据需要动态调整。链表实现栈的好处是可以灵活处理大规模数据,适合需要频繁插入和删除操作的场景。
示例代码(链表实现栈):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义链表节点结构
struct Node {
int data;
struct Node* next;
};
typedef struct {
struct Node* top;
} Stack;
// 初始化栈
void initStack(Stack *s) {
s->top = NULL;
}
// 判断栈是否为空
bool isEmpty(Stack *s) {
return s->top == NULL;
}
// 压栈操作
void push(Stack *s, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
}
// 出栈操作
int pop(Stack *s) {
if (isEmpty(s)) {
printf("栈为空,无法出栈\n");
return -1; // 返回一个无效值表示错误
}
struct Node* temp = s->top;
int value = temp->data;
s->top = temp->next;
free(temp);
return value;
}
// 查看栈顶元素
int peek(Stack *s) {
if (isEmpty(s)) {
printf("栈为空,无法查看栈顶\n");
return -1; // 返回一个无效值表示错误
}
return s->top->data;
}
int main() {
Stack s;
initStack(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
printf("栈顶元素: %d\n", peek(&s));
printf("出栈元素: %d\n", pop(&s));
printf("栈顶元素: %d\n", peek(&s));
return 0;
}
输出:
cpp
栈顶元素: 30
出栈元素: 30
栈顶元素: 20
栈的应用案例
-
函数调用栈:在程序执行过程中,函数的调用和返回使用栈来管理。每次调用一个函数时,当前的执行状态(如返回地址、局部变量等)会被压入栈中,函数返回时再从栈中弹出。
-
括号匹配:在编译器中,栈可以用于检查括号是否匹配。例如,左括号压入栈,右括号出栈,最后栈为空则表示括号匹配。
-
逆波兰表达式计算:栈可以用于计算后缀表达式(逆波兰表达式)。操作数压入栈,遇到操作符时弹出栈顶的两个操作数进行计算,结果再压入栈。
-
浏览器历史记录:浏览器的前进和后退按钮可以使用栈来实现。后退时将当前页面地址出栈,前进时将之前出栈的页面地址重新压入栈。
总结
栈是一种简单但强大的数据结构,遵循LIFO原则,适合处理需要后进先出的场景。栈的基本操作包括压栈、出栈、查看栈顶和判断栈空/满。栈可以通过数组或链表实现,数组实现适合小规模数据处理,链表实现适合动态调整大小。栈在函数调用、括号匹配、表达式计算等场景中有广泛的应用。