目录
堆栈(Stack)重要的数据结构,它们在计算机科学和软件开发中都有广泛的应用。下面我将分别解释这种数据结构的特点和用途。
一、堆栈的基本概念
1.1 堆栈定义
堆栈是一种后进先出(LIFO,Last In First Out)的数据结构。它只允许在一端(称为栈顶)进行插入和删除操作。顺序栈和共享栈是最经典的堆栈方式
1.2 堆栈操作
压栈(Push):向堆栈中添加一个元素,该元素被放置在栈顶。
弹栈(Pop):从堆栈中移除一个元素,该元素总是栈顶的元素。
查看栈顶(Peek):查看栈顶的元素,但不移除它。
1.3 堆栈应用
函数调用和返回:在大多数编程语言中,函数调用和返回都使用堆栈来实现。当一个函数被调用时,它的局部变量和返回地址被压入堆栈。当函数返回时,这些信息从堆栈中弹出。
表达式求值:在编译器和解释器中,堆栈用于存储中间结果和操作符,以支持算术和逻辑表达式的求值。
后退/前进功能:在图形用户界面(GUI)中,如浏览器或文本编辑器,堆栈可以用来实现后退和前进功能。
内存管理:在某些编程语言和系统中,堆栈也用于管理内存。例如,在 C 和 C++ 中,局部变量和函数参数通常存储在堆栈上。
浏览器历史记录:在网页浏览器中,后退和前进按钮通常使用堆栈来管理历史记录。当用户浏览新的网页时,该网页的 URL 被推入堆栈。当用户点击后退按钮时,最近的 URL 从堆栈中弹出并显示。
这些只是堆栈的一些基本应用,实际上堆栈在计算机科学中的用途远不止这些。
二、顺序栈
2.1 定义
栈的顺序存储即使用连续的空间存储栈中的元素,顺序方式存储的栈称顺序栈。
2.2 操作
2.3 C语言实现
#include <stdio.h>
#include <stdlib.h>
#define STACK_SIZE 100
typedef struct {
int data[STACK_SIZE];
int top;
} SeqStack;
// 初始化顺序栈
void initStack(SeqStack *s) {
s->top = -1;
}
// 判断栈是否为空
int isEmpty(SeqStack *s) {
return s->top == -1;
}
// 判断栈是否已满
int isFull(SeqStack *s) {
return s->top == STACK_SIZE - 1;
}
// 入栈操作
int push(SeqStack *s, int value) {
if (isFull(s)) {
printf("栈已满,无法进行入栈操作。\n");
return 0;
}
s->data[++s->top] = value;
return 1;
}
// 出栈操作
int pop(SeqStack *s, int *value) {
if (isEmpty(s)) {
printf("栈为空,无法进行出栈操作。\n");
return 0;
}
*value = s->data[s->top--];
return 1;
}
// 打印栈内元素
void printStack(SeqStack *s) {
if (isEmpty(s)) {
printf("栈为空。\n");
return;
}
for (int i = s->top; i >= 0; i--) {
printf("%d ", s->data[i]);
}
printf("\n");
}
int main() {
SeqStack stack;
initStack(&stack);
// 入栈操作
push(&stack, 1);
push(&stack, 2);
push(&stack, 3);
// 出栈操作
int value;
pop(&stack, &value);
printf("出栈元素: %d\n", value);
// 打印栈内元素
printStack(&stack);
return 0;
}
这段代码定义了一个简单的顺序栈,并实现了初始化、判断空满、入栈、出栈以及打印栈内元素的基本操作。在main
函数中演示了如何使用这些操作。
三、共享栈
3.1 定义
当需要同时使用多个栈时,可以在同一块连续的空间中设置多个栈,形成多个栈共享栈空间。当其中的某个栈满时,可能其它栈尚余许多空间,这时可以让其它的堆栈进行左右移动,然后让该栈继续获得一些新的空间,从而可以继续进行进栈活动。
3.2 操作
共享栈初始状态:
共享栈中间某状态:
3.3 C语言实现
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *elements; // 存储元素的数组
int top_index; // 栈顶索引
int capacity; // 栈的容量
} SharedStack;
// 初始化共享栈
SharedStack* init_shared_stack(int capacity) {
SharedStack* stack = (SharedStack*)malloc(sizeof(SharedStack));
stack->elements = (int*)malloc(capacity * sizeof(int));
stack->top_index = -1;
stack->capacity = capacity;
return stack;
}
// 释放共享栈占用的内存
void free_shared_stack(SharedStack* stack) {
free(stack->elements);
free(stack);
}
// 判断共享栈是否为空
int is_shared_stack_empty(SharedStack* stack) {
return stack->top_index == -1;
}
// 判断共享栈是否已满
int is_shared_stack_full(SharedStack* stack) {
return stack->top_index + 1 == stack->capacity;
}
// 向共享栈中添加元素
void push_shared_stack(SharedStack* stack, int element) {
if (is_shared_stack_full(stack)) {
printf("Stack is full.\n");
return;
}
stack->elements[++stack->top_index] = element;
}
// 从共享栈中移除元素
int pop_shared_stack(SharedStack* stack) {
if (is_shared_stack_empty(stack)) {
printf("Stack is empty.\n");
return -1;
}
return stack->elements[stack->top_index--];
}
// 主函数示例
int main() {
SharedStack* stack = init_shared_stack(10); // 初始化容量为10的共享栈
push_shared_stack(stack, 1); // 添加元素
push_shared_stack(stack, 2);
printf("Pop element: %d\n", pop_shared_stack(stack)); // 移除元素
printf("Pop element: %d\n", pop_shared_stack(stack));
free_shared_stack(stack); // 释放栈占用的内存
return 0;
}
这个简单的共享栈实现包括初始化、释放内存、判断栈满或空、进栈和出栈的基本操作。在主函数中,我们创建了一个容量为10的共享栈,添加了两个元素,然后移除并打印了这两个元素,最后释放了栈占用的内存。
四、链式栈
4.1 定义
链式栈是一种数据存储结构,可以通过单链表的方式来实现,使用链式栈的优点在于它能够克服用数组实现的顺序栈空间利用率不高的特点,但是需要为每个栈元素分配额外的指针空间用来存放指针域。
4.2 操作
每个结点存储元素和指针,栈顶指针top用于指向处于栈顶的结点,即单链表中的首结点。链式栈的进、出栈总是在首结点位置进行。
4.3 C语言实现
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
typedef struct Stack {
Node* top;
int size;
} Stack;
Stack* createStack() {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->top = NULL;
stack->size = 0;
return stack;
}
int isEmpty(Stack* stack) {
return stack->top == NULL;
}
void push(Stack* stack, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = stack->top;
stack->top = newNode;
stack->size++;
}
int pop(Stack* stack) {
if (isEmpty(stack)) {
fprintf(stderr, "Stack is empty\n");
exit(1);
}
Node* topNode = stack->top;
int data = topNode->data;
stack->top = stack->top->next;
free(topNode);
stack->size--;
return data;
}
int peek(Stack* stack) {
if (isEmpty(stack)) {
fprintf(stderr, "Stack is empty\n");
exit(1);
}
return stack->top->data;
}
int main() {
Stack* stack = createStack();
push(stack, 1);
push(stack, 2);
push(stack, 3);
printf("Top element: %d\n", peek(stack));
printf("Popped element: %d\n", pop(stack));
printf("Top element: %d\n", peek(stack));
return 0;
}
在这个实现中,我们定义了两个结构体:Node和Stack。Node用来表示链表的节点,每个节点包含一个整数和一个指向下一个节点的指针。Stack结构体包含一个指向顶部节点的指针和一个表示栈中元素数量的整数。
我们实现了几个基本的操作:
-
createStack:创建一个新的空栈。
-
isEmpty:检查栈是否为空。
-
push:将一个元素添加到栈顶。
-
pop:移除并返回栈顶元素。
-
peek:返回栈顶元素但不移除它。
在main函数中,我们创建一个新的空栈,然后进行入栈、查看栈顶元素、出栈和再次查看栈顶元素的操作。
五、总结
堆栈和队列都是重要的数据结构,但它们的特性和用途是不同的。堆栈遵循后进先出的原则,主要用于需要保存和恢复临时状态的情况,如函数调用和表达式求值。队列遵循先进先出的原则,主要用于需要按顺序处理元素的情况,如打印任务、线程调度和消息传递。