跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

数据结构基础:栈与队列的顺序及链式实现

栈和队列是特殊的线性表,栈遵循后进先出原则,队列遵循先进先出原则。详细讲解了顺序栈、共享栈、链栈的定义与操作,包括初始化、进栈、出栈及获取栈顶元素。同时涵盖了顺序队列的循环处理方案(如增加 size 或 tag 变量)以及链式队列的带头结点与不带头结点实现。通过对比顺序存储与链式存储的优缺点,帮助读者掌握数据结构的核心逻辑与代码实现细节。

漫步发布于 2026/3/26更新于 2026/7/2632 浏览
数据结构基础:栈与队列的顺序及链式实现

一、前言

我们在前两篇文章中讲到顺序表和链表其都是线性结构,我们今天讲的栈和队列也是特殊的线性表。顺序表和链表没有所谓的进出限制,但是我们今天要讲的栈就不一样,它有特殊的进栈和出栈顺序,只允许在一端进行插入和删除。也就是说后进先出,先进后出。但是队列呢,只允许从前面插入,后面出。也就是他俩是特殊的线性结构,所以在基本操作上和前文的线性表和链表有一定相似之处。

二、栈

只允许在一端进行插入和删除操作的线性表。

空栈,没有元素。

栈顶允许插入和删除,栈底不允许。

2.1 顺序栈

就是用顺序方式存储的栈就是顺序栈。

顺序栈的定义

这里用 top 指针来标记栈顶位置,初始化时 top = -1,表示栈是空的。就像刚买的空盘子架,还没放任何盘子。

#define MAXSIZE 100 // 栈的最大容量
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct {
    int data[100]; // 栈的数组
    int top;       // 栈顶指针
} SqStack; // 顺序栈的类型定义
初始化
// 初始化操作
void InitStack(SqStack *S) {
    S->top = -1; // 将栈顶指针设为 -1
}

// 判断栈是否为空
bool StackEmpty(SqStack *S) {
    if (S->top == -1) {
        return true; // 栈为空
    } else {
        return false; // 栈不为空
    }
}
进栈操作

进栈就像往盘子架上放新盘子,只能放在最上面:

// 进栈操作
bool Push(SqStack *S, int x) {
    if (S->top == MAXSIZE) {
        return false; // 栈满
    }
    S->top++; // 栈顶指针加 1
    S->data[S->top] = x; // 将 x 入栈
    return true; // 入栈成功
}

先检查栈是不是满了(top 等于最大容量),没满的话就把 top 往上挪一位,再把数据放进去。

出栈操作

出栈则是从最上面拿走一个盘子:

// 出栈操作
bool Pop(SqStack *S, int *x) {
    if (S->top == -1) {
        return false; // 栈为空
    }
    *x = S->data[S->top]; // 将栈顶元素出栈
    S->top--; // 栈顶指针减 1
    return true; // 出栈成功
}

先检查栈是不是空的,不空的话就把栈顶元素取出来,再把 top 往下挪一位。

获取栈顶元素

有时候我们只想看看最上面的盘子是啥样,不想拿走它:

// 获取栈顶元素
bool GetTop(SqStack *S, int *x) {
    if (S->top == -1) {
        return false; // 栈为空
    }
    *x = S->data[S->top]; // 将栈顶元素赋值给 x
    return true; // 获取成功
}

这个操作和出栈的区别是,top 指针不会移动,只是'偷看'一眼。

2.2 共享栈

共享栈是个节省空间的小能手,它让两个栈共享同一块数组空间,栈底分别在数组的两端,向中间生长。

建立
// 共享栈建立
typedef struct {
    int data[100]; // 栈的数组
    int top;       // 栈顶指针
    int top1;      // 栈底指针
} SharedStack; // 共享栈的类型定义
初始化

初始化时,第一个栈的 top 在 -1,第二个栈的 top1 在最大容量处:

// 初始化
void InitSharedStack(SharedStack *S) {
    S->top = -1;   // 将栈顶指针设为 -1
    S->top1 = MAXSIZE; // 将栈顶指针设为最大容量
}

这样两个栈就可以向中间扩展,直到 top + 1 == top1 时,表示栈满。

2.3 链栈

链栈就是用链表实现的栈,链表的头结点作为栈顶,这样进栈和出栈操作都能在 O(1) 时间内完成,比顺序栈更灵活(不用提前规定大小)。

建立
// 链栈建立
typedef struct LinkNode {
    int data;
    struct LinkNode *next;
} LinkNode;

typedef LinkNode* LinkStack; // 链栈的类型定义

这里栈顶指针就是链表的头指针,进栈就是在头结点前插入新节点,出栈就是删除头结点。

三、队列

队列和栈正好相反,它是'先进先出'(FIFO,First In First Out)的,就像排队买奶茶——先到的人先拿到奶茶。只能在队尾插入(入队),在队头删除(出队)。

顺序队列用数组实现,但有个小问题:如果单纯地让 front 指向队头,rear 指向队尾,随着入队和出队操作,front 和 rear 都会往后移动,可能导致数组前面的空间浪费。

定义

初始化时,front 和 rear 都指向 0:

// 队列定义
typedef struct {
    int data[100]; // 队列的数组
    int front;     // 队头指针
    int rear;      // 队尾指针
} SqQueue; // 顺序队列的类型定义

初始化队列

// 初始化队列
void InitQueue(SqQueue *Q) {
    Q->front = Q->rear = 0; // 将队头指针和队尾指针设为 0
}

判断队列是否为空

// 判断队列是否为空
bool QueueEmpty(SqQueue *Q) {
    if (Q->front == Q->rear) {
        return true; // 队列为空
    } else {
        return false; // 队列不为空
    }
}

入队操作

// 入队操作
bool EnQueue(SqQueue *Q, int x) {
    if (Q->rear == MAXSIZE) {
        return false; // 队列满
    }
    Q->data[Q->rear] = x; // 将 x 入队
    Q->rear++;            // 队尾指针加 1
    return true;          // 入队成功
}

把数据放在 rear 指向的位置,再把 rear 往后挪一位。

出队操作

// 出队操作
bool DeQueue(SqQueue *Q, int *x) {
    if (Q->front == Q->rear) {
        return false; // 队列为空
    }
    *x = Q->data[Q->front]; // 将队头元素赋值给 x
    Q->front++;             // 队头指针加 1
    return true;            // 出队成功
}

取出 front 指向的元素,再把 front 往后挪一位。

判断队列的满和空

法一:

上面的方法有个缺陷:rear 到达数组末尾时,即使前面有空位,也会被判为队满。解决这个问题有几种方法:

// 获取队头元素
bool GetHead(SqQueue Q, int *x) {
    if (Q.rear == Q.front) // 队列为空
        return false;
    *x = Q.data[Q.front]; // 将队头元素赋值给 x
    return true;
}
法二:定义长度问题

增加一个 size 变量记录队列长度,队满条件是 size == MaxSize,队空条件是 size == 0。

#define MaxSize 10
typedef struct {
    int data[10];
    int front, rear;
    int size; // 队列当前长度
} SqQueue;

// 插入成功:size++ 删除成功 size--
// 初始化时:rear=front=0, size=0
// 队满条件:size==MaxSize
// 队空条件:size==0
法三:

增加一个 tag 变量,记录最近操作是插入(1)还是删除(0)。队满条件是 front == rear && tag == 1,队空条件是 front == rear && tag == 0。

#define MaxSize 10
typedef struct {
    int data[10];
    int front, rear;
    int tag; // 最近进行的是删除/插入
               // 初始化时,rear=front=0; tag=0
} SqQueue;

// 每次删除操作成功时,都令 tag=0
// 每次插入操作成功时,都令 tag=1
// 只有删除操作,才可能导致队空,只有插入操作,才可能导致队满
// 队满条件:front == rear && tag == 1
// 队空条件:front == rear && tag == 0

链式存储实现队列

链式队列用链表实现,队头指针指向头结点,队尾指针指向最后一个节点,这样入队和出队操作都很方便。

定义一个链式队列
// 链队列的节点类型定义
typedef struct LinkNode {
    int data;              // 数据域
    struct LinkNode *next; // 指向下一个节点的指针
} LinkNode;

// 链队列的类型定义
typedef struct {
    LinkNode *front, *rear; // 队头指针和队尾指针
} LinkQueue;
初始化(带头结点)

头结点不存数据,只是为了操作方便。

// 初始化链队列
void InitQueue(LinkQueue *Q) {
    // 初始时队头指针和队尾指针都指向头结点
    Q->front = Q->rear = (LinkNode *)malloc(sizeof(LinkNode));
    Q->front->next = NULL; // 头结点的 next 指针设为 NULL
}

// 判断队列是否为空
bool QueueEmpty(LinkQueue *Q) {
    if (Q->front == Q->rear) {
        return true; // 队列为空
    } else {
        return false; // 队列不为空
    }
}
初始化队列不带头结点
// 判断队列是否为空
bool QueueEmpty(LinkQueue *Q) {
    if (Q->front == Q->rear) {
        return true; // 队列为空
    } else {
        return false; // 队列不为空
    }
}
入队(带头结点)

入队就是在队尾添加新节点,然后把 rear 移到新节点。

// 入队操作(带头结点)
bool EnQueue(LinkQueue *Q, int x) {
    // 创建一个新节点
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    if (s == NULL) {
        return false; // 内存分配失败
    }
    s->data = x;           // 将 x 赋值给新节点的数据域
    s->next = NULL;        // 将新节点的 next 指针设为 NULL
    Q->rear->next = s;     // 将原队尾节点的 next 指针指向新节点
    Q->rear = s;           // 将队尾指针指向新节点
    return true;           // 入队成功
}
入队(不带头结点)
// 入队操作(不带头结点)
bool EnQueue(LinkQueue *Q, int x) {
    // 创建一个新节点
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    if (s == NULL) {
        return false; // 内存分配失败
    }
    s->data = x;           // 将 x 赋值给新节点的数据域
    s->next = NULL;        // 将新节点的 next 指针设为 NULL

    // 若队列为空(首次入队),队头和队尾都指向新节点
    if (QueueEmpty(Q)) {
        Q->front = s;
        Q->rear = s;
    } else {
        // 队列非空时,队尾节点的 next 指向新节点,再移动队尾指针
        Q->rear->next = s;
        Q->rear = s;
    }
    return true; // 入队成功
}

不带头结点的链式队列入队操作,核心区别在于需要处理'首次入队'的特殊情况:

  • 当队列是空的时候(front 和 rear 都为 NULL),新节点既是队头也是队尾,所以 front 和 rear 要同时指向这个新节点
  • 非空队列时,操作和带头结点类似:让当前队尾的 next 指向新节点,再把 rear 移到新节点上
出队(带头结点)
// 出队操作(带头结点)
bool DeQueue(LinkQueue *Q, int *x) {
    // 队列为空时无法出队
    if (QueueEmpty(Q)) {
        return false;
    }
    LinkNode *p = Q->front->next; // p 指向队头元素节点
    *x = p->data;                 // 保存出队元素的值
    Q->front->next = p->next;     // 头结点跳过队头元素,指向其后继

    // 若出队的是最后一个元素,队尾指针需指向头结点(保持空队列状态)
    if (Q->rear == p) {
        Q->rear = Q->front;
    }
    free(p); // 释放出队节点的内存
    return true;
}
  • 队头元素始终是 front->next 指向的节点(头结点不存储数据)
  • 出队时只需修改头结点的 next 指针,最后一个元素出队时需将 rear 重置为头结点
出队(不带头结点)
// 出队操作(不带头结点)
bool DeQueue(LinkQueue *Q, int *x) {
    // 队列为空时无法出队
    if (QueueEmpty(Q)) {
        return false;
    }
    LinkNode *p = Q->front; // p 指向队头元素节点(不带头结点时 front 直接指向队头)
    *x = p->data;           // 保存出队元素的值

    // 若队列只有一个元素,出队后队头队尾均置空
    if (Q->front == Q->rear) {
        Q->front = Q->rear = NULL;
    } else {
        // 队列有多个元素时,队头指针后移
        Q->front = Q->front->next;
    }
    free(p); // 释放出队节点的内存
    return true;
}
  • 队头指针 front 直接指向队头元素(首个数据节点)
  • 出队时需直接移动 front 指针,最后一个元素出队后需将 front 和 rear 均置为 NULL
队列满的条件

顺序存储,预分配的空间耗尽时队满。

链式存储,一般不会队满,除非内存不足。

四、总结

栈和队列都是特殊的线性表,只是对操作的位置做了限制:

  • 栈:只允许在栈顶操作,后进先出
  • 队列:只允许在队尾入队、队头出队,先进先出

它们的实现可以用数组(顺序存储)或链表(链式存储),各有优缺点:

  • 顺序存储:访问快,但大小固定(或需要扩容)
  • 链式存储:大小灵活,但访问需要遍历指针

就像两种不同的数据处理风格:栈是'后来者居上',队列是'按规矩办事',各有各的适用场景。掌握它们的逻辑和实现,对于理解更复杂的数据结构至关重要。

目录

  1. 一、前言
  2. 二、栈
  3. 2.1 顺序栈
  4. 顺序栈的定义
  5. 初始化
  6. 进栈操作
  7. 出栈操作
  8. 获取栈顶元素
  9. 2.2 共享栈
  10. 建立
  11. 初始化
  12. 2.3 链栈
  13. 建立
  14. 三、队列
  15. 定义
  16. 初始化队列
  17. 判断队列是否为空
  18. 入队操作
  19. 出队操作
  20. 判断队列的满和空
  21. 法一:
  22. 法二:定义长度问题
  23. 法三:
  24. 链式存储实现队列
  25. 定义一个链式队列
  26. 初始化(带头结点)
  27. 初始化队列不带头结点
  28. 入队(带头结点)
  29. 入队(不带头结点)
  30. 出队(带头结点)
  31. 出队(不带头结点)
  32. 队列满的条件
  33. 四、总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 小说生成器本地部署与配置指南
  • Rust 异步 Web 框架 Axum:核心原理与实战进阶
  • Git 实战:如何精准合并指定分支的特定提交
  • 云开发 Copilot:AI 赋能的低代码开发
  • C++11 核心特性详解:列表初始化、新式声明、范围 for 与 STL 变化
  • AIGC 时代 C++ 吞吐量优化技巧与性能提升实践
  • SketchUp STL 插件安装与使用指南
  • MySQL 库核心操作详解:创建、修改与备份实战
  • LM Studio 本地离线部署大语言模型实战指南
  • 大模型上下文窗口 200k 到底是什么
  • 机场出租车调度问题的数学建模与 Python 仿真实现
  • MySQL 常用函数整理与使用指南
  • LLM 微调:时机、方法与抉择
  • 基于 DeepSeek-R1-Distill-Llama-8B 的 OpenSpec 协议分析
  • 渗透测试入门书单与章节脉络
  • 安卓手机通过 Termux 和 Alpine 部署 Docker 并实现外网访问
  • Unity Shader Graph Triplanar 节点原理解析与实战
  • STL 转 STEP 格式转换工具 stltostp 使用指南
  • 高效集成 Gemini API:Zotero 学术场景 AI 辅助分析指南
  • OpenClaw 智能体框架入门:环境搭建、模型配置与远程访问

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online