栈是一种遵循后进先出(LIFO)原则的线性数据结构。它在计算机科学中有广泛的应用,从表达式求值到函数调用的实现。本文将介绍栈的两种常见实现方式:基于数组和基于链表,并讨论栈的实际应用场景。
1. 基于数组的栈实现
数组实现的栈具有固定大小,但操作简单快速。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int top;
} ArrayStack;
void initStack(ArrayStack *s) {
s->top = -1;
}
int isEmpty(ArrayStack *s) {
return s->top == -1;
}
int isFull(ArrayStack *s) {
return s->top == MAX_SIZE - 1;
}
void push(ArrayStack *s, int value) {
if (isFull(s)) {
printf("Stack Overflow\n");
return;
}
s->items[++(s->top)] = value;
}
int pop(ArrayStack *s) {
if (isEmpty(s)) {
printf("Stack Underflow\n");
return -1;
}
return s->items[(s->top)--];
}
int main() {
ArrayStack stack;
initStack(&stack);
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
printf("Popped: %d\n", pop(&stack));
printf("Popped: %d\n", pop(&stack));
return 0;
}
/* 运行结果:
Popped: 30
Popped: 20
*/
数组实现的优点是访问顶部元素的时间复杂度为O(1),缺点是大小固定,可能会导致栈溢出。
2. 基于链表的栈实现
链表实现的栈可以动态增长,避免了栈溢出的问题。
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
typedef struct {
Node* top;
} LinkedStack;
void initStack(LinkedStack *s) {
s->top = NULL;
}
int isEmpty(LinkedStack *s) {
return s->top == NULL;
}
void push(LinkedStack *s, int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed\n");
return;
}
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
}
int pop(LinkedStack *s) {
if (isEmpty(s)) {
printf("Stack Underflow\n");
return -1;
}
Node* temp = s->top;
int value = temp->data;
s->top = s->top->next;
free(temp);
return value;
}
int main() {
LinkedStack stack;
initStack(&stack);
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
printf("Popped: %d\n", pop(&stack));
printf("Popped: %d\n", pop(&stack));
return 0;
}
/* 运行结果:
Popped: 30
Popped: 20
*/
链表实现的优点是可以动态增长,缺点是每个元素都需要额外的内存来存储下一个节点的指针。
3. 栈的应用场景
栈在计算机科学和日常编程中有许多重要的应用:
- 函数调用:程序的函数调用栈用于跟踪子程序的执行。
- 表达式求值:栈可用于计算后缀(逆波兰)表达式。
- 括号匹配:检查括号是否正确嵌套和平衡。
- 深度优先搜索:在图和树的遍历算法中使用。
- 回溯算法:在解决迷宫问题、八皇后问题等回溯算法中使用。
- 内存管理:某些内存分配策略使用栈来管理内存。
- 撤销操作:在文本编辑器中实现撤销功能。
- 浏览器历史:存储访问过的页面以实现后退功能。
示例:使用栈进行括号匹配
下面是一个使用栈来检查括号是否匹配的简单例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 100
typedef struct {
char items[MAX_SIZE];
int top;
} Stack;
void initStack(Stack *s) { s->top = -1; }
int isEmpty(Stack *s) { return s->top == -1; }
void push(Stack *s, char c) { s->items[++(s->top)] = c; }
char pop(Stack *s) { return s->items[(s->top)--]; }
int isMatching(char open, char close) {
return (open == '(' && close == ')') ||
(open == '[' && close == ']') ||
(open == '{' && close == '}');
}
int areParenthesesBalanced(char *expr) {
Stack stack;
initStack(&stack);
for (int i = 0; expr[i]; i++) {
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{') {
push(&stack, expr[i]);
} else if (expr[i] == ')' || expr[i] == ']' || expr[i] == '}') {
if (isEmpty(&stack) || !isMatching(pop(&stack), expr[i])) {
return 0; // Not balanced
}
}
}
return isEmpty(&stack); // Stack should be empty if balanced
}
int main() {
char expr[] = "{[()]}";
if (areParenthesesBalanced(expr))
printf("Balanced\n");
else
printf("Not Balanced\n");
return 0;
}
/* 运行结果:
Balanced
*/
这个例子展示了栈在解决实际问题中的应用。通过使用栈,我们可以轻松地检查一个表达式中的括号是否正确匹配。
总结
栈是一种简单yet强大的数据结构。无论是基于数组还是链表实现,栈都在许多编程场景中发挥着关键作用。理解栈的工作原理和实现方法,对于解决各种编程问题和理解计算机系统的内部工作机制都有很大帮助。在实际应用中,选择数组还是链表实现取决于具体需求,如是否需要动态大小、内存使用效率等因素。