1、什么是栈和队列
栈的定义
栈(Stack)是一种遵循后进先出(Last In First Out,简称LIFO)原则的数据结构。栈可以看作是一个容器,元素被加入和删除的地方只有一端,这一端被称为栈顶(top),另一端被称为栈底(bottom)。在栈中,新元素被添加到栈顶,任何时候只能访问栈顶的元素。删除元素时也是从栈顶开始,即最后添加的元素最先被删除。
栈的示意图
队列的定义
队列(Queue)是一种遵循先进先出(First In First Out,简称FIFO)原则的线性数据结构。队列可以看作是一个容器,元素被加入和删除的地方分别称为队尾(rear)和队头(front),新元素被添加到队尾,而删除元素时则从队头开始。
队列的示意图
2、栈和队列的实现方式
数组实现
通过数组实现栈
栈需要支持两个操作:压入(push)和弹出(pop)。我们可以使用一个数组来实现它。
首先,我们需要定义一个数组和一个栈顶指针。栈顶指针指向当前栈顶元素的位置。
#define MAX_SIZE 10 // 定义栈的最大大小
int stack[MAX_SIZE]; // 定义栈
int top = -1; // 初始化栈顶指针
压入操作(push)就是将元素放到栈顶,并将栈顶指针加一。如果栈已满,则无法执行该操作。
void push(int element) {
if (top >= MAX_SIZE - 1) { // 栈已满
printf("Stack is full\n");
} else { // 栈未满
top++; // 将栈顶指针加一
stack[top] = element; // 将元素放到栈顶
}
}
弹出操作(pop)就是将栈顶元素取出,并将栈顶指针减一。如果栈为空,则无法执行该操作。
int pop() {
if (top < 0) { // 栈为空
printf("Stack is empty\n");
return -1;
} else { // 栈非空
int element = stack[top]; // 取出栈顶元素
top--; // 将栈顶指针减一
return element;
}
}
通过数组实现队列
队列需要支持两个操作:入队(enqueue)和出队(dequeue)。我们可以使用一个数组来实现它。
首先,我们需要定义一个数组、一个队头指针和一个队尾指针。队头指针指向当前队头元素的位置,队尾指针指向下一个可用的位置。
#define MAX_SIZE 10 // 定义队列的最大大小
int queue[MAX_SIZE]; // 定义队列
int front = 0; // 初始化队头指针
int rear = 0; // 初始化队尾指针
入队操作(enqueue)就是将元素放到队尾,并将队尾指针加一。如果队列已满,则无法执行该操作。`
void enqueue(int element) {
if ((rear + 1) % MAX_SIZE == front) { // 队列已满
printf("Queue is full\n");
} else { // 队列未满
queue[rear] = element; // 将元素放到队尾
rear = (rear + 1) % MAX_SIZE; // 将队尾指针加一
}
}
链表实现
通过链表实现栈
首先,我们需要定义链表节点的结构体,它应该至少包含两个成员:一个数据成员和一个指向下一个节点的指针。
typedef struct Node {
int data;
struct Node* next;
} Node;
接下来,我们可以定义一个栈结构体,其中包含一个指向栈顶的指针。要初始化栈,只需把栈顶指针设置为NULL即可。`
typedef struct Stack {
Node* top;
} Stack;
void init(Stack* stack) {
stack->top = NULL;
}
当我们要往栈中插入一个元素时,我们需要创建一个新的节点,将数据存储在这个节点中,并把这个节点插入到栈顶。具体操作如下:
void push(Stack* stack, int value) {
Node* node = (Node*) malloc(sizeof(Node));
node->data = value;
node->next = stack->top;
stack->top = node;
}
当我们要从栈中取出一个元素时,我们只需要从栈顶取出这个节点,并将栈顶指针指向下一个节点即可。
int pop(Stack* stack) {
if (stack->top == NULL) {
printf("Stack is empty!\n");
return -1;
}
int value = stack->top->data;
Node* temp = stack->top;
stack->top = stack->top->next;
free(temp);
return value;
}
通过链表实现队列
定义链表节点的方法与使用链表实现栈时相同。
typedef struct Node {
int data;
struct Node* next;
} Node;
我们可以定义一个队列结构体,其中包含两个指针:一个指向队列头部的指针和一个指向队列尾部的指针。要初始化队列,只需把这两个指针都设置为NULL即可。
typedef struct Queue {
Node* head;
Node* tail;
} Queue;
void init(Queue* queue) {
queue->head = NULL;
queue->tail = NULL;
}
我们往队列中插入一个元素时,需要创建一个新节点,将数据存储在这个节点中,并把这个节点插入到队列尾部。具体操作如下:
void enqueue(Queue* queue, int value) {
Node* node = (Node*) malloc(sizeof(Node));
node->data = value;
node->next = NULL;
if (queue->tail == NULL) {
queue->head = node;
queue->tail = node;
} else {
queue->tail->next = node;
queue->tail = node;
}
}
从队列中取出元素时,我们需要从队列的头部开始取出节点,并将队列头部指针指向下一个节点即可。
int dequeue(Queue* queue) {
if (queue->head == NULL) {
printf("Queue is empty!\n");
return -1;
}
int value = queue->head->data;
Node* temp = queue->head;
3、栈和队列的应用
栈的应用场景
栈是一种常见的数据结构,其遵循先进后出(LIFO)的原则。栈广泛应用于计算机科学领域的各个方面,如编译器、操作系统、数据结构等。下面给出几个栈在实际应用中的例子,并以C语言为例:
- 表达式求值:编译器通常使用栈来解析和评估表达式。例如,可以使用栈来实现中缀表达式转换为后缀表达式并进行求值。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_SIZE 100
int stack[MAX_SIZE];
int top = -1;
void push(int val) {
if (top >= MAX_SIZE - 1) {
printf("Stack overflow\n");
exit(1);
}
stack[++top] = val;
}
int pop() {
if (top == -1) {
printf("Stack underflow\n");
exit(1);
}
return stack[top--];
}
int evaluate_postfix(char* postfix) {
int i, val, op1, op2;
for (i = 0; postfix[i] != '\0'; i++) {
if (isdigit(postfix[i])) {
push(postfix[i] - '0');
} else {
op2 = pop();
op1 = pop();
switch (postfix[i]) {
case '+':
val = op1 + op2;
break;
case '-':
val = op1 - op2;
break;
case '*':
val = op1 * op2;
break;
case '/':
val = op1 / op2;
break;
default:
printf("Invalid operator\n");
exit(1);
}
push(val);
}
}
return pop();
}
int main() {
char postfix[] = "23+4*5-";
int result = evaluate_postfix(postfix);
printf("Result: %d\n", result);
return 0;
}
- 函数调用:当一个函数被调用时,CPU将指令指针(程序计数器)压入栈中。当函数返回时,CPU从栈中弹出指令指针并恢复执行。
#include <stdio.h>
void func2() {
printf("Inside func2()\n");
}
void func1() {
printf("Inside func1()\n");
func2();
}
int main() {
printf("Inside main()\n");
func1();
printf("Exiting main()\n");
return 0;
}
- 内存分配:许多操作系统使用栈来管理内存分配和释放。例如,当一个函数被调用时,操作系统为其分配一段内存空间,并在函数返回时释放这些内存。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
int* create_array(int size) {
int* arr = (int*) malloc(size * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
return arr;
}
int main() {
int size = 10;
int* arr = create_array(size);
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
队列的应用场景
- 线程池:假设我们有一个线程池来处理一些任务,我们需要使用队列来管理这些任务。在 C语言中,我们可以定义一个队列结构体来表示队列的属性,然后编写入队和出队函数来操作队列。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 10
struct Queue {
int front;
int rear;
int data[MAX_SIZE];
};
void enqueue(struct Queue *q, int value) {
if (q->rear == MAX_SIZE - 1) {
printf("Queue is full!\n");
} else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->data[q->rear] = value;
}
}
int dequeue(struct Queue *q) {
if (q->front == -1 || q->front > q->rear) {
printf("Queue is empty!\n");
return -1;
} else {
int value = q->data[q->front];
q->front++;
return value;
}
}
int main() {
struct Queue q = {-1, -1, {0}};
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
printf("Dequeued value: %d\n", dequeue(&q));
printf("Dequeued value: %d\n", dequeue(&q));
return 0;
}
- 操作系统调度:我们可以使用队列来实现操作系统的进程调度。在 C
语言中,我们可以使用结构体来表示每个进程的属性,然后将它们存储在队列中,按照优先级顺序排列。然后执行队列头部的进程,直到队列为空。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 10
struct Process {
int id;
int priority;
};
struct Queue {
int front;
int rear;
struct Process data[MAX_SIZE];
};
void enqueue(struct Queue *q, struct Process p) {
if (q->rear == MAX_SIZE - 1) {
printf("Queue is full!\n");
} else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->data[q->rear] = p;
}
}
struct Process dequeue(struct Queue *q) {
if (q->front == -1 || q->front > q->rear) {
printf("Queue is empty!\n");
struct Process p = {-1, -1};
return p;
} else {
struct Process p = q->data[q->front];
q->front++;
return p;
}
}
int main() {
struct Process p1 = {100, 3};
struct Process p2 = {101, 1};
struct Process p3 = {102, 2};
struct Queue q = {-1, -1, {{0}}};
enqueue(&q, p1);
enqueue(&q, p2);
enqueue(&q, p3);
while (q.front <= q.rear) {
struct Process p = dequeue(&q);
printf("Process id: %d, priority: %d\n", p.id, p.priority);
}
return 0;
}
- 计算机网络:我们可以使用队列来管理网络协议栈中的数据包。在 C
语言中,我们可以定义一个数据包结构体来表示数据包的属性,然后将它们存储在队列中,按照发送顺序排列。然后从队列头部取出数据包并进行处理,直到队列为空。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 10
struct Packet {
int id;
int size;
};
struct Queue {
int front;
int rear;
struct Packet data[MAX_SIZE];
};
void enqueue(struct Queue *q, struct Packet p) {
if (q->rear == MAX_SIZE - 1) {
printf("Queue is full!\n");
} else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->data[q->rear] = p;
}
}
struct Packet dequeue(struct Queue *q) {
if (q->front == -1 || q->front > q->rear) {
printf("Queue is empty!\n");
struct Packet p = {-1, -1};
return p;
} else {
struct Packet p = q->data[q->front];
q->front++;
return p;
}
}
int main() {
struct Packet p1 = {100, 1024};
struct Packet p2 = {101, 512};
struct Packet p3 = {102, 2048};
struct Queue q = {-1, -1, {{0}}};
enqueue(&q, p1);
enqueue(&q, p2);
enqueue(&q, p3);
while (q.front <= q.rear) {
struct Packet p = dequeue(&q);
printf("Packet id: %d, size: %d\n", p.id, p.size);
}
return 0;
}
4、栈和队列的区别和联系
栈和队列的区别
- 栈和队列是两种常见的数据结构,都是线性结构。它们之间最主要的区别在于元素的添加和删除顺序不同。
- 栈是一种后进先出(LIFO)的数据结构。这意味着最后被添加到栈中的元素最先被取出。栈只允许在栈顶进行元素的添加和删除操作,因此称为后进先出。当需要对某个元素进行处理时,只能从栈顶开始弹出元素。栈的基本操作包括push、pop、peek、is_empty和is_full。其中,push方法将元素压入栈顶,pop方法将栈顶元素弹出并返回,peek方法返回栈顶元素但不弹出,is_empty方法判断栈是否为空,is_full方法判断栈是否已满(仅针对固定大小的数组实现)。
- 队列是一种先进先出(FIFO)的数据结构。这意味着最先被添加到队列中的元素最先被取出。队列允许在队尾进行元素的添加,在队首进行元素的删除操作,因此称为先进先出。队列的基本操作包括enqueue、dequeue、peek、is_empty和is_full。其中,enqueue方法将元素插入队列的末尾,dequeue方法从队列的头部删除一个元素并返回,peek方法返回队列头部的元素但不删除,is_empty方法判断队列是否为空,is_full方法判断队列是否已满(仅针对固定大小的数组实现)。
- 栈和队列在应用中有着广泛的运用。栈常用于对数据的暂存或延迟处理,以及表达式求值、函数调用等场景。而队列常用于实现缓存、任务调度、消息传递等场景,它们需要按照先进先出的顺序进行处理。
- 在编写程序时,我们需要根据具体的需求选择合适的数据结构。如果需要在某个操作后再次处理该操作的结果,可以使用栈;如果需要按照先进先出的顺序处理元素,可以使用队列。
栈和队列的联系
虽然栈和队列在很多方面有不同的特点和操作方法,但它们也有一些共同点,下面是它们的联系:
- 线性结构:栈和队列都是线性结构,它们的元素都可以通过一定的顺序排列。栈中的元素只能从栈顶进出,而队列中的元素只能从队头出,从队尾进。
- 存储方式:栈和队列都可以使用数组或链表来实现。
- 操作方法:栈和队列都有基本的操作方法,如插入、删除、查看等。虽然它们的方法名称可能不同,但它们的作用大致相同。
- 应用场景:栈和队列都有广泛的应用场景,如计算机程序设计、操作系统、数据压缩、网络通信等。
- 计算机底层实现:在计算机底层,栈和队列都是非常重要的数据结构。比如函数调用就是通过栈来实现的,操作系统的任务调度也是通过队列来实现的。
- 数据结构基础:栈和队列都是数据结构中比较基础的部分。其他许多复杂的数据结构如图、树等都与它们相关联。
- 限制性操作:栈和队列都具有一定的限制性操作。栈只允许从顶部添加或删除元素,而队列只允许在队尾添加元素,在队首删除元素。这种限制性的操作可以减少程序出错的可能性。
- 嵌套关系:栈和队列可以互相嵌套使用。比如,在计算某个表达式的值时,可以使用一个栈来保存数字和运算符,再使用一个队列来处理括号。
- 算法实现:许多算法都涉及到栈和队列的使用。例如,深度优先搜索(DFS)中使用栈,广度优先搜索(BFS)中使用队列。
- 可扩展性:虽然数组实现的栈和队列大小固定,但是对于链表实现的栈和队列来说,它们的大小是可扩展的。
希望这篇博客能够给大家带来收获,如果有错误的地方还希望各位大佬多多指正,欢迎评论!