此文章为本人学习笔记,若有错误求指正。
一、栈(Stack)
1. 栈的基本概念
栈(Stack)是一种操作受限的线性表,遵循“后进先出”(LIFO, Last In, First Out)原则。栈只能在一端进行插入和删除操作,这一端被称为栈顶(Top)。最早进入栈的元素被压在栈底,最后进入的元素位于栈顶,最先被取出。
2. 栈的基本操作
- Push(压栈):将元素压入栈顶。
- Pop(出栈):从栈顶弹出元素。
- Peek(查看栈顶元素):获取栈顶元素的值,但不弹出元素。
- IsEmpty(判断栈是否为空):检查栈是否为空。
- Clear(清空栈):移除栈中的所有元素。
3. 栈的应用场景
- 浏览器前进、后退功能:使用栈来保存用户的浏览历史,实现前进和后退操作。
- 编译器与进程:函数调用时,栈用来保存函数调用信息(如局部变量、返回地址等)。
- 括号匹配与语法分析:栈用于检测编程语言中的括号匹配问题。
- 表达式转换:中缀表达式转后缀表达式使用栈来保存操作符。
- 后缀表达式求值:使用栈来求解后缀表达式。
- 进制转换:将数字从一种进制转换到另一种进制时,使用栈来保存中间结果。
4. 栈的实现
栈可以通过两种方式实现:顺序栈(基于数组)和链式栈(基于链表)。
4.1 顺序栈
顺序栈使用数组来存储栈中的元素。由于数组的大小固定,因此顺序栈的容量是有限的。在实现时,需要一个变量来记录栈顶元素的索引。
顺序栈的实现代码
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack *stack) {
stack->top = -1;
}
// 判断栈是否为空
int isEmpty(Stack *stack) {
return stack->top == -1;
}
// 压栈
void push(Stack *stack, int value) {
if (stack->top == MAX_SIZE - 1) {
printf("栈满,无法压栈。\n");
return;
}
stack->data[++(stack->top)] = value;
}
// 出栈
int pop(Stack *stack) {
if (isEmpty(stack)) {
printf("栈空,无法出栈。\n");
exit(1);
}
return stack->data[(stack->top)--];
}
// 查看栈顶元素
int peek(Stack *stack) {
if (isEmpty(stack)) {
printf("栈空。\n");
exit(1);
}
return stack->data[stack->top];
}
// 清空栈
void clear(Stack *stack) {
stack->top = -1;
}
4.2 链式栈
链式栈使用链表来实现。链表中的每个节点包含一个数据元素和指向下一个节点的指针。链式栈没有容量限制,可以根据需要动态扩展。
链式栈的实现代码
#include <stdio.h>
#include <stdlib.h>
// 链式栈节点结构
typedef struct Node {
int data;
struct Node *next;
} Node;
// 栈结构
typedef struct {
Node *top;
} Stack;
// 初始化栈
void initStack(Stack *stack) {
stack->top = NULL;
}
// 判断栈是否为空
int isEmpty(Stack *stack) {
return stack->top == NULL;
}
// 压栈
void push(Stack *stack, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (!newNode) {
printf("内存分配失败。\n");
exit(1);
}
newNode->data = value;
newNode->next = stack->top;
stack->top = newNode;
}
// 出栈
int pop(Stack *stack) {
if (isEmpty(stack)) {
printf("栈空,无法出栈。\n");
exit(1);
}
Node *temp = stack->top;
int value = temp->data;
stack->top = temp->next;
free(temp);
return value;
}
// 查看栈顶元素
int peek(Stack *stack) {
if (isEmpty(stack)) {
printf("栈空。\n");
exit(1);
}
return stack->top->data;
}
// 清空栈
void clear(Stack *stack) {
while (!isEmpty(stack)) {
pop(stack);
}
}
二、队列(Queue)
1. 队列的基本概念
队列(Queue)是一种操作受限的线性表,遵循“先进先出”(FIFO, First In, First Out)原则。队列允许在一端(队尾)插入元素,在另一端(队头)删除元素。队列中的第一个元素被最早插入,最后被删除。
2. 队列的基本操作
- Enqueue(入队):将元素插入队尾。
- Dequeue(出队):从队头删除元素。
- Peek(查看队头元素):获取队头元素的值,但不删除元素。
- IsEmpty(判断队列是否为空):检查队列是否为空。
- Clear(清空队列):移除队列中的所有元素。
3. 队列的应用场景
- 网络请求:处理网络请求时,使用队列来存储等待处理的请求。
- 事件处理:GUI系统中,将事件存入队列,然后依次分发处理。
- 消息队列(MQ):消息队列在异步通信中被广泛使用,如Kafka等消息中间件。
- 线程池:线程池使用队列来管理和调度线程任务。
4. 队列的实现
队列可以通过两种方式实现:顺序队列(基于数组)和链式队列(基于链表)。
4.1 顺序队列
顺序队列使用数组来存储元素,并通过两个指针分别指向队头和队尾。为了有效利用数组空间,常使用循环队列的形式来避免“假溢出”。
顺序队列的实现代码
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} Queue;
// 初始化队列
void initQueue(Queue *queue) {
queue->front = 0;
queue->rear = 0;
}
// 判断队列是否为空
int isEmpty(Queue *queue) {
return queue->front == queue->rear;
}
// 入队
void enqueue(Queue *queue, int value) {
if ((queue->rear + 1) % MAX_SIZE == queue->front) {
printf("队列满,无法入队。\n");
return;
}
queue->data[queue->rear] = value;
queue->rear = (queue->rear + 1) % MAX_SIZE;
}
// 出队
int dequeue(Queue *queue) {
if (isEmpty(queue)) {
printf("队列空,无法出队。\n");
exit(1);
}
int value = queue->data[queue->front];
queue->front = (queue->front + 1) % MAX_SIZE;
return value;
}
// 查看队头元素
int peek(Queue *queue) {
if (isEmpty(queue)) {
printf("队列空。\n");
exit(1);
}
return queue->data[queue->front];
}
// 清空队列
void clear(Queue *queue) {
queue->front = queue->rear = 0;
}
4.2 链式队列
链式队列使用链表来实现,每个节点包含一个数据元素和指向下一个节点的指针。链式队列不受限于数组的固定容量,可以根据需要动态扩展。
链式队列的实现代码
#include <stdio.h>
#include <stdlib.h>
// 链式队列节点结构
typedef struct Node {
int data;
struct Node *next;
} Node;
// 队列结构
typedef struct {
Node *front;
Node *rear;
} Queue;
// 初始化队列
void initQueue(Queue *queue) {
queue->front = queue->rear = NULL;
}
// 判断队列是否为空
int isEmpty(Queue *queue) {
return queue->front == NULL;
}
// 入队
void enqueue(Queue *queue, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (!newNode) {
printf("内存分配失败。\n");
exit(1);
}
newNode->data = value;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}
// 出队
int dequeue(Queue *queue) {
if (isEmpty(queue)) {
printf("队列空,无法出队。\n");
exit(1);
}
Node *temp = queue->front;
int value = temp->data;
queue->front = temp->next;
if (queue->front == NULL) {
queue->rear = NULL;
}
free(temp);
return value;
}
// 查看队头元素
int peek(Queue *queue) {
if (isEmpty(queue)) {
printf("队列空。\n");
exit(1);
}
return queue->front->data;
}
// 清空队列
void clear(Queue *queue) {
while (!isEmpty(queue)) {
dequeue(queue);
}
}
三、总结
栈和队列作为操作受限的线性数据结构,在算法设计和系统开发中有着广泛的应用。栈以其后进先出的特性,在函数调用、表达式求值、括号匹配等场景中扮演重要角色。队列则因其先进先出的特性,广泛用于任务调度、消息传递等场合。通过对栈和队列的深入理解和实现,可以为掌握更复杂的数据结构和算法打下坚实的基础。