栈
栈(Stack)是一种常见的数据结构,遵循后进先出(LIFO,Last In First Out)的原则。这意味着最后进入栈的元素将被最先移除。栈可以比喻为一叠盘子,你只能在顶部放置或移除盘子。
栈的基本操作:
- 压栈(Push):向栈顶插入一个元素。
- 出栈(Pop):从栈顶移除一个元素。
- 查看栈顶元素(Peek):查看栈顶的元素,但不对栈做出任何改变。
栈的特点:
- 后进先出(LIFO):最后压入栈的元素会被最先弹出。
- 限制性操作:栈的插入和删除操作仅限制在栈顶。
- 内存管理:栈内存是有限的,过多的压栈操作可能导致栈溢出。
- 应用广泛:栈在计算机科学中有着广泛的应用,如函数调用、表达式求值、语法分析、撤销操作等。
栈的实现方式:
- 数组实现:使用数组来存储栈中的元素。通过维护一个栈顶指针来跟踪栈顶元素。
- 链表实现:使用链表来表示栈结构。每个节点包含数据和指向下一个节点的指针。
栈的应用场景:
- 函数调用:函数调用时会使用栈来保存调用信息。
- 表达式求值:中缀表达式转换为后缀表达式时会用到栈。
- 浏览器的后退按钮:浏览历史记录可以使用栈来管理。
- 逆序输出:逆序输出字符串或其他数据。
- 撤销操作:保存历史状态以便撤销操作。
栈是一种简单但非常有用的数据结构,能够解决许多算法和应用中的问题。
使用数组实现栈
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack *stack) {
stack->top = -1;
}
// 判断栈是否为空
int isEmpty(Stack *stack) {
return stack->top == -1;
}
// 判断栈是否已满
int isFull(Stack *stack) {
return stack->top == MAX_SIZE - 1;
}
// 入栈操作
void push(Stack *stack, int item) {
if (!isFull(stack)) {
stack->items[++stack->top] = item;
} else {
printf("Stack is full. Cannot push element.\n");
}
}
// 出栈操作
int pop(Stack *stack) {
if (!isEmpty(stack)) {
return stack->items[stack->top--];
} else {
printf("Stack is empty. Cannot pop element.\n");
return -1;
}
}
// 查看栈顶元素
int peek(Stack *stack) {
if (!isEmpty(stack)) {
return stack->items[stack->top];
} else {
printf("Stack is empty. Cannot peek.\n");
return -1;
}
}
int main() {
Stack stack;
initStack(&stack);
push(&stack, 1);
push(&stack, 2);
push(&stack, 3);
printf("%d\n", pop(&stack)); // Output: 3
printf("%d\n", peek(&stack)); // Output: 2
return 0;
}
使用链表实现栈
#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 item) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = item;
newNode->next = stack->top;
stack->top = newNode;
}
// 出栈操作
int pop(Stack *stack) {
if (!isEmpty(stack)) {
Node *temp = stack->top;
int item = temp->data;
stack->top = temp->next;
free(temp);
return item;
} else {
printf("Stack is empty. Cannot pop element.\n");
return -1;
}
}
// 查看栈顶元素
int peek(Stack *stack) {
if (!isEmpty(stack)) {
return stack->top->data;
} else {
printf("Stack is empty. Cannot peek.\n");
return -1;
}
}
int main() {
Stack stack;
initStack(&stack);
push(&stack, 1);
push(&stack, 2);
push(&stack, 3);
printf("%d\n", pop(&stack)); // Output: 3
printf("%d\n", peek(&stack)); // Output: 2
return 0;
}
使用数组实现栈的优缺点:
优点:
- 简单直观: 数组实现栈相对简单直观,易于理解和实现。
- 随机访问: 数组支持随机访问,可以直接通过索引访问栈中的元素。
- 空间效率高: 数组在内存中是连续存储的,不需要额外的指针存储空间。
缺点:
- 固定大小: 数组实现的栈大小通常是固定的,无法动态扩展,可能会导致栈溢出。
- 复杂度高: 在数组实现中,插入和删除元素的操作可能需要移动大量元素,时间复杂度为 O(n)。
- 浪费空间: 如果栈大小预先设定过大,可能会浪费内存空间。
使用链表实现栈的优缺点:
优点:
- 动态大小: 链表实现的栈大小可以动态增长或缩小,更加灵活。
- 插入删除高效: 在链表中插入和删除元素的操作时间复杂度为 O(1),不需要移动其他元素。
- 不会栈溢出: 链表实现的栈不会固定大小,不存在栈溢出的问题。
缺点:
- 额外空间开销: 链表实现需要额外的指针空间存储节点间的连接关系,占用更多的内存。
- 不支持随机访问: 链表不支持直接随机访问元素,需要从头节点开始遍历到目标节点。
- 复杂度较高: 链表的实现相对复杂一些,需要处理节点之间的链接关系。
根据需要选择数组或链表实现栈,如果需要动态大小和高效的插入删除操作,链表实现可能更合适;如果栈大小固定且随机访问较为频繁,数组实现可能更适合。
队列
队列是一种常用的数据结构,遵循先进先出(First In First Out,FIFO)的原则。在队列中,数据项按照其进入队列的顺序进行处理,即最先进入队列的元素最先被取出,类似于现实生活中排队等候的场景。
队列的基本概念:
- 入队(Enqueue): 向队列的末尾添加元素的操作。
- 出队(Dequeue): 从队列的头部移除元素的操作。
- 队首(Front): 队列中的第一个元素。
- 队尾(Rear): 队列中的最后一个元素。
- 空队列(Empty Queue): 不包含任何元素的队列。
- 满队列(Full Queue): 队列已达到最大容量,无法再添加新元素。
队列的结构:
队列通常有两种主要的实现方式:数组(顺序队列)和链表(链式队列)。
-
数组实现的队列(顺序队列):
- 使用数组作为底层存储结构,通过两个指针 front 和 rear 分别指示队头和队尾位置。
- 入队时将元素添加到 rear 指向的位置,出队时从 front 指向的位置移除元素。
- 队列满时可能需要进行元素搬移操作。
-
链表实现的队列(链式队列):
- 使用链表作为底层存储结构,每个节点包含数据和指向下一个节点的指针。
- 队列的头部由头指针指示,尾部由尾指针指示。
- 入队时在链表尾部添加新节点,出队时从链表头部移除节点。
- 链式队列不会存在队列满的情况,可以动态地增加或减少元素。
无论是数组实现还是链表实现,队列都是一种非常有用的数据结构,常用于模拟排队、任务调度等场景,在计算机科学中有着广泛的应用。
使用数组实现队列
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
} Queue;
void initQueue(Queue *queue) {
queue->front = -1;
queue->rear = -1;
}
int isEmpty(Queue *queue) {
return queue->front == -1;
}
int isFull(Queue *queue) {
return (queue->front == 0 && queue->rear == MAX_SIZE - 1) || (queue->front == queue->rear + 1);
}
void enqueue(Queue *queue, int item) {
if (isFull(queue)) {
printf("Queue is full. Cannot enqueue element.\n");
} else {
if (isEmpty(queue)) {
queue->front = 0;
queue->rear = 0;
} else if (queue->rear == MAX_SIZE - 1) {
queue->rear = 0;
} else {
queue->rear++;
}
queue->items[queue->rear] = item;
}
}
int dequeue(Queue *queue) {
int item;
if (isEmpty(queue)) {
printf("Queue is empty. Cannot dequeue element.\n");
return -1;
} else {
item = queue->items[queue->front];
if (queue->front == queue->rear) {
queue->front = -1;
queue->rear = -1;
} else if (queue->front == MAX_SIZE - 1) {
queue->front = 0;
} else {
queue->front++;
}
return item;
}
}
int peek(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty. Cannot peek.\n");
return -1;
} else {
return queue->items[queue->front];
}
}
int main() {
Queue queue;
initQueue(&queue);
enqueue(&queue, 1);
enqueue(&queue, 2);
enqueue(&queue, 3);
printf("%d\n", dequeue(&queue)); // Output: 1
printf("%d\n", peek(&queue)); // Output: 2
return 0;
}
使用链表实现队列
#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 = NULL;
queue->rear = NULL;
}
int isEmpty(Queue *queue) {
return queue->front == NULL;
}
void enqueue(Queue *queue, int item) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = item;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = newNode;
} else {
queue->rear->next = newNode;
}
queue->rear = newNode;
}
int dequeue(Queue *queue) {
int item;
if (isEmpty(queue)) {
printf("Queue is empty. Cannot dequeue element.\n");
return -1;
} else {
Node *temp = queue->front;
item = temp->data;
queue->front = temp->next;
free(temp);
if (queue->front == NULL) {
queue->rear = NULL;
}
return item;
}
}
int peek(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty. Cannot peek.\n");
return -1;
} else {
return queue->front->data;
}
}
int main() {
Queue queue;
initQueue(&queue);
enqueue(&queue, 1);
enqueue(&queue, 2);
enqueue(&queue, 3);
printf("%d\n", dequeue(&queue)); // Output: 1
printf("%d\n", peek(&queue)); // Output: 2
return 0;
}
循环队列
循环队列(Circular Queue)是一种队列的实现方式,通过在数组或链表的结构中建立循环链接,使得队列在到达结尾时能够回到开头,实现队列空间的循环利用。循环队列避免了普通队列在出队操作后可能出现的空闲位置无法再次利用的问题,提高了队列的空间利用效率。
循环队列的特点:
-
循环利用空间: 循环队列通过循环链接的方式,实现队列空间的循环利用,避免了普通队列可能出现的空闲位置无法再次利用的问题。
-
指针循环移动: 循环队列中的队头和队尾指针在操作时会进行循环移动,确保在数组或链表结构中正确地循环遍历。
-
解决队列空间不足问题: 循环队列能够更高效地利用队列的空间,避免了队列空间不足的问题,使得队列能够继续接收新的元素。
实现循环队列的关键操作:
-
初始化队列: 初始化队列时,需要设置队头和队尾指针,通常初始时都指向 -1。
-
入队操作: 将元素添加到队列的尾部,同时移动队尾指针,确保循环移动到数组或链表的开头。
-
出队操作: 从队列的头部移除元素,同时移动队头指针,确保循环移动到数组或链表的开头。
-
判断队列是否为空或已满: 需要实现判断队列是否为空和是否已满的函数,避免非法操作。
循环队列是一种重要的数据结构,在计算机科学中有着广泛的应用,特别适用于需要高效利用空间并且要求元素有序进出的场景。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5
typedef struct {
int items[MAX_SIZE];
int front, rear;
int size;
} CircularQueue;
// 初始化循环队列
void initQueue(CircularQueue *queue) {
queue->front = -1; // 初始化队列头指针
queue->rear = -1; // 初始化队列尾指针
queue->size = 0; // 初始化队列大小
}
// 判断队列是否已满
int isFull(CircularQueue *queue) {
return (queue->front == 0 && queue->rear == MAX_SIZE - 1) || (queue->front == queue->rear + 1);
}
// 判断队列是否为空
int isEmpty(CircularQueue *queue) {
return queue->front == -1;
}
// 入队操作
void enqueue(CircularQueue *queue, int item) {
if (isFull(queue)) {
printf("Queue is full. Cannot enqueue element.\n");
} else {
if (queue->front == -1) {
queue->front = 0;
}
queue->rear = (queue->rear + 1) % MAX_SIZE; // 循环移动尾指针
queue->items[queue->rear] = item;
queue->size++;
}
}
// 出队操作
int dequeue(CircularQueue *queue) {
int item;
if (isEmpty(queue)) {
printf("Queue is empty. Cannot dequeue element.\n");
return -1;
} else {
item = queue->items[queue->front];
if (queue->front == queue->rear) {
queue->front = -1;
queue->rear = -1;
} else {
queue->front = (queue->front + 1) % MAX_SIZE; // 循环移动头指针
}
queue->size--;
return item;
}
}
// 获取队首元素
int peek(CircularQueue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty. Cannot peek.\n");
return -1;
} else {
return queue->items[queue->front];
}
}
int main() {
CircularQueue queue;
initQueue(&queue);
enqueue(&queue, 1);
enqueue(&queue, 2);
enqueue(&queue, 3);
printf("%d\n", dequeue(&queue)); // 输出:1
printf("%d\n", peek(&queue)); // 输出:2
return 0;
}
在循环队列的实现中,需要注意:
- 使用取模运算来实现队列头尾指针的循环移动,确保在数组边界处正确循环。
- 队列为空时,front 和 rear 都指向 -1;队列满时,满足
(front == 0 && rear == MAX_SIZE - 1)
或(front == rear + 1)
。 - 当 front 和 rear 相等时,表示队列中只有一个元素。