栈和队列(C语言)

文章目录

    • 前言
    • 一、栈的概念、应用与结构
      • 1.1 栈的定义与特性
      • 1.2 栈的应用场景
      • 1.3 栈的逻辑结构示意
    • 二、栈的多种实现
      • 2.1 顺序栈(基于数组)
        • 2.1.1 数据结构
        • 2.1.2 主要操作
        • 2.1.3 优缺点
      • 2.2 链式栈(基于单链表)
        • 2.2.1 数据结构
        • 2.2.2 主要操作
        • 2.2.3 优缺点
    • 三、队列的概念、应用与结构
      • 3.1 队列的定义与特性
      • 3.2 队列的应用场景
      • 3.3 队列的逻辑结构示意
    • 四、队列的多种实现
      • 4.1 顺序队列(基于数组)
        • 4.1.1 数据结构
        • 4.1.2 主要操作
        • 4.1.3 问题
      • 4.2 循环队列
        • 4.2.1 数据结构
        • 4.2.2 主要操作
        • 4.2.3 优点
      • 4.3 链式队列
        • 4.3.1 数据结构
        • 4.3.2 主要操作
    • 五、复杂度与选型建议
    • 总结

前言

栈(Stack)和队列(Queue)是最基础、最常用的两种线性数据结构。

  • :遵循“后进先出(LIFO)”原则,常用于函数调用管理、表达式求值、括号匹配等场景;
  • 队列:遵循“先进先出(FIFO)”原则,常用于任务调度、缓存管理、广度优先搜索等场景。

本篇博客将从概念、典型应用、实现细节,到性能分析,分步骤带你深入掌握各类实现方法。

栈和队列(C语言)_第1张图片


一、栈的概念、应用与结构

1.1 栈的定义与特性

  • 定义:栈是一种只能在一端进行插入和删除操作的线性结构。
  • 操作:入栈(Push)、出栈(Pop)、取栈顶(Top/Peek)、判空(IsEmpty)。
  • 特性
    • 后进先出(LIFO)
    • 操作时间复杂度均为 O(1)

1.2 栈的应用场景

  1. 函数调用:程序运行时,操作系统用调用栈保存返回地址与局部变量。
  2. 表达式求值:中缀表达式转后缀、后缀表达式计算都依赖双栈算法。
  3. 括号匹配:编译器检查源代码括号是否成对匹配。
  4. 图的深度优先搜索(DFS):可用显式栈代替递归调用。

1.3 栈的逻辑结构示意

┌───────┐    ← 栈顶 (top)
│   30  │
│   20  │
│   10  │
└───────┘    ← 栈底

二、栈的多种实现

2.1 顺序栈(基于数组)

2.1.1 数据结构
#define MAXSIZE 100

typedef struct {
    int data[MAXSIZE];
    int top;        // 指向当前栈顶元素的下标,-1 表示空栈
} SeqStack;
2.1.2 主要操作
// 初始化
void InitSeqStack(SeqStack *s) {
    s->top = -1;
}

// 入栈
int PushSeq(SeqStack *s, int v) {
    if (s->top >= MAXSIZE - 1) return 0;  // 栈满
    s->data[++s->top] = v;
    return 1;
}

// 出栈
int PopSeq(SeqStack *s, int *v) {
    if (s->top == -1) return 0;          // 栈空
    *v = s->data[s->top--];
    return 1;
}

// 读栈顶
int TopSeq(SeqStack *s, int *v) {
    if (s->top == -1) return 0;
    *v = s->data[s->top];
    return 1;
}

// 判空
int IsEmptySeq(SeqStack *s) {
    return s->top == -1;
}
2.1.3 优缺点
  • 优点:实现简单、访问速度快。
  • 缺点:容量固定,易出现“栈满”问题;插入和删除始终在尾部。

2.2 链式栈(基于单链表)

2.2.1 数据结构
typedef struct StackNode {
    int data;
    struct StackNode *next;
} StackNode, *LinkStack;
2.2.2 主要操作
// 初始化
void InitLinkStack(LinkStack *s) {
    *s = NULL;
}

// 入栈:在链表头插入
void PushLink(LinkStack *s, int v) {
    StackNode *node = (StackNode*)malloc(sizeof(StackNode));
    node->data = v;
    node->next = *s;
    *s = node;
}

// 出栈:删除链表头
int PopLink(LinkStack *s, int *v) {
    if (*s == NULL) return 0;
    StackNode *tmp = *s;
    *v = tmp->data;
    *s = tmp->next;
    free(tmp);
    return 1;
}

// 取栈顶
int TopLink(LinkStack s, int *v) {
    if (s == NULL) return 0;
    *v = s->data;
    return 1;
}

// 判空
int IsEmptyLink(LinkStack s) {
    return s == NULL;
}
2.2.3 优缺点
  • 优点:动态分配,空间利用率高,可存储更多数据;
  • 缺点:每次入/出栈都要 malloc/free,额外开销大。

三、队列的概念、应用与结构

3.1 队列的定义与特性

  • 定义:队列是一种只能在一端插入(队尾)、另一端删除(队头)的线性结构。
  • 操作:入队(Enqueue)、出队(Dequeue)、取队头(Front)、判空(IsEmpty)。
  • 特性
    • 先进先出(FIFO)
    • 基本操作时间复杂度均为 O(1)

3.2 队列的应用场景

  1. 操作系统进程调度:就绪队列按照先来先服务。
  2. 缓存/缓冲区:网络数据包、IO 缓冲区常用循环队列。
  3. 广度优先搜索(BFS):图和树遍历。
  4. 异步消息处理:生产者—消费者模型。

3.3 队列的逻辑结构示意

front → [10] [20] [30] ← rear

四、队列的多种实现

4.1 顺序队列(基于数组)

4.1.1 数据结构
#define MAXQ 100

typedef struct {
    int data[MAXQ];
    int front;      // 指向队头元素下标
    int rear;       // 指向队尾下一个位置
} SeqQueue;
4.1.2 主要操作
// 初始化
void InitSeqQueue(SeqQueue *q) {
    q->front = q->rear = 0;
}

// 入队
int EnQueue(SeqQueue *q, int v) {
    if (q->rear == MAXQ) return 0; // 队满
    q->data[q->rear++] = v;
    return 1;
}

// 出队
int DeQueue(SeqQueue *q, int *v) {
    if (q->front == q->rear) return 0; // 队空
    *v = q->data[q->front++];
    return 1;
}

// 取队头
int Front(SeqQueue *q, int *v) {
    if (q->front == q->rear) return 0;
    *v = q->data[q->front];
    return 1;
}

// 判空
int IsEmptyQ(SeqQueue *q) {
    return q->front == q->rear;
}
4.1.3 问题
  • 随着 DeQueue 操作,front 索引不断增大,数组前部空间无法复用。

4.2 循环队列

4.2.1 数据结构
#define MAXQ 100

typedef struct {
    int data[MAXQ];
    int front;      // 指向队头
    int rear;       // 指向队尾的下一个位置
} CircQueue;
4.2.2 主要操作
// 初始化
void InitCircQueue(CircQueue *q) {
    q->front = q->rear = 0;
}

// 判满:下一位置等于 front 则满
int IsFullCirc(CircQueue *q) {
    return (q->rear + 1) % MAXQ == q->front;
}

// 入队
int EnCirc(CircQueue *q, int v) {
    if (IsFullCirc(q)) return 0;
    q->data[q->rear] = v;
    q->rear = (q->rear + 1) % MAXQ;
    return 1;
}

// 出队
int DeCirc(CircQueue *q, int *v) {
    if (IsEmptyQ((SeqQueue*)q)) return 0; // front==rear
    *v = q->data[q->front];
    q->front = (q->front + 1) % MAXQ;
    return 1;
}
4.2.3 优点
  • 空间利用率高,不会“假溢出”;
  • 逻辑与顺序队列相同,但索引循环使用。

4.3 链式队列

4.3.1 数据结构
typedef struct QNode {
    int data;
    struct QNode *next;
} QNode, *QueuePtr;

typedef struct {
    QueuePtr front, rear;
} LinkQueue;
4.3.2 主要操作
// 初始化
void InitLinkQueue(LinkQueue *q) {
    q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
    q->front->next = NULL;
}

// 入队:在链尾插入
void EnLink(LinkQueue *q, int v) {
    QueuePtr node = (QueuePtr)malloc(sizeof(QNode));
    node->data = v;
    node->next = NULL;
    q->rear->next = node;
    q->rear = node;
}

// 出队:删除链头第一个节点
int DeLink(LinkQueue *q, int *v) {
    if (q->front == q->rear) return 0; // 空队
    QueuePtr tmp = q->front->next;
    *v = tmp->data;
    q->front->next = tmp->next;
    if (q->rear == tmp) q->rear = q->front;
    free(tmp);
    return 1;
}

五、复杂度与选型建议

实现方式 空间复杂度 入/出操作 优点 适用场景
顺序栈 O(n) O(1) 简单、访问快 元素个数上限已知、对性能要求高
链式栈 O(n) O(1) 动态、无栈满限制 元素个数动态、空间不预先分配
顺序队列 O(n) O(1) 简单 入/出次数少、不关注循环复用
循环队列 O(n) O(1) 空间高效、不需搬移元素 缓冲区、IO 队列、实时任务调度
链式队列 O(n) O(1) 动态、无队满限制 元素动态、空闲链表场景

总结

  1. 理解本质:栈“后进先出”,队列“先进先出”;
  2. 灵活选型:根据元素规模、空间/性能需求,选用顺序或链式实现;
  3. 掌握变体:循环队列可解决数组队列的“假溢出”问题;

你可能感兴趣的:(数据结构,c语言,开发语言,数据结构,栈和队列)