文章目录
[1.1 栈的概念及结构](#1.1 栈的概念及结构)
[1.2 栈的实现](#1.2 栈的实现)
[1.2.1 动态栈的结构定义](#1.2.1 动态栈的结构定义)
[1.2.2 接口实现](#1.2.2 接口实现)
[2.1 队列的概念及结构](#2.1 队列的概念及结构)
[2.2 队列的实现](#2.2 队列的实现)
[2.2.1 链式队列的结构定义](#2.2.1 链式队列的结构定义)
[2.2.2 接口实现](#2.2.2 接口实现)
前言
栈(Stack)和队列(Queue)是两种非常重要的线性数据结构,它们在实际开发中有着广泛的应用。栈遵循后进先出(LIFO)的原则,而队列遵循先进先出(FIFO)的原则。本文将从概念入手,详细讲解它们的结构特点,并给出使用C语言的完整实现代码,帮助读者深入理解这两种数据结构。
一、栈
1.1 栈的概念及结构
栈是一种特殊的线性表,其数据元素的插入和删除操作只能在固定的一端进行。允许插入和删除的一端称为栈顶 ,另一端称为栈底。栈的这种操作特性决定了它天然适合处理需要"回溯"或"撤销"的场景,比如函数调用、括号匹配等。
栈的插入操作称为压栈 (push),删除操作称为出栈(pop)。由于只能在栈顶操作,所以最后压入的元素总是最先弹出,这就是后进先出的含义。这里也可以看成一个一端封住,另一端没有封住的物品来理解,进出都只能通过没有封住的那一端来进行。
1.2 栈的实现
栈可以使用数组或链表实现。相对而言,数组 结构更优,因为数组在尾部插入删除的时间复杂度为O(1),并且CPU缓存命中率更高。我们通常实现一个动态增长的栈,以便在运行时根据需要扩容,静态栈就是数组的大小已经固定的,相关可以参考动态顺序表以及静态顺序表。
1.2.1 动态栈的结构定义
cpp
typedef int Stackdatatype;
typedef struct stack
{
Stackdatatype* arr;
int top;
int capacity;
}stack;
关于 top 的两种定义方式:
-
如果
top指向栈顶元素的下一个位置,则空栈时top == 0,栈中元素个数为top。 -
如果
top指向栈顶元素,则空栈时top == -1,栈中元素个数为top + 1。
相关图文理解:

本文采用第一种方式,即 _top 初始为0,表示栈顶下一个位置。
1.2.2 接口实现
下面给出动态栈的常用接口实现。
cpp
//栈的初始化以及销毁
void StackInit(stack* pst);
void StackDestroy(stack* pst);
//入栈出栈
void StackPush(stack* pst,Stackdatatype data);
void Stackpop(stack* pst);
Stackdatatype StackTop(stack* pst);//取栈顶元素
//判空
bool Stackempty(stack* pst);
//获取个数
int Stacksize(stack* pst);
void StackInit(stack* pst)
{
assert(pst!= NULL);
pst->arr = NULL;
//这里的top指向的是栈顶元素的下一个位置
pst->top = pst->capacity = 0;
}
void StackDestroy(stack* pst)
{
assert(pst!= NULL);
if (pst->arr)
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
void StackPush(stack* pst, Stackdatatype data)
{
assert(pst!= NULL);
//首先判断栈空间是否已经满了
if (pst->top == pst->capacity)
{
int newcapacity = (pst->capacity == 0)? 4 : 2 * pst->capacity;
//这里在原来的基础上进行扩容
Stackdatatype* newarr = (Stackdatatype*)realloc
(pst->arr,newcapacity * sizeof(Stackdatatype));
if (newarr == NULL)
{
perror("malloc failed");
exit(1);
}
//对相关数据进行更新
pst->arr = newarr;
pst->capacity = newcapacity;
}
//栈空间未满,直接存入数据
pst->arr[pst->top++] = data;
}
void Stackpop(stack* pst)
{
assert(pst!= NULL);
assert(pst->top != 0);
pst->top--;
}
Stackdatatype StackTop(stack* pst)
{
assert(pst!= NULL&&pst->top!= 0);
return pst->arr[pst->top - 1];
}
bool Stackempty(stack* pst)
{
assert(pst!= NULL);
return pst->top == 0;
}
int Stacksize(stack* pst)
{
assert(pst!= NULL);
return pst->top;
}
注意:出栈和获取栈顶元素前都需要确保栈非空,这里使用 assert 进行断言,实际应用中可根据需要返回错误码。
二、队列
2.1 队列的概念及结构
队列也是一种特殊的线性表,它只允许在一端(队尾 )进行插入操作,在另一端(队头)进行删除操作。这就像排队买东西,先到的人先服务,即先进先出。
2.2 队列的实现
队列可以使用数组或链表实现。由于在数组头部删除元素需要移动大量数据,效率较低,因此通常使用链表结构来实现队列。
2.2.1 链式队列的结构定义
队列需要同时维护队头和队尾指针,以便快速执行入队和出队操作。因此我们定义两个结构体:一个是节点结构体,一个是队列结构体。
cpp
typedef int QListdataType;
typedef struct QListNode //这里是队列的节点结构
{
struct QListNode* next;
QListdataType data;
}Qnode;
//这里是队列的结构,这里是为了方便后面的操作,所以使用了结构体
//包含了队列的头指针,尾指针,队列的大小
//否则我们需要多次遍历操作才能实现队列
typedef struct Queue
{
Qnode* phead;
Qnode* ptail;
int size;
}Que;
2.2.2 接口实现
cpp
//队列的初始化以及销毁函数
void QueInit(Que* pq);
void QueDestroy(Que* pq);
//队列的入队操作
void Quepush(Que* pq, QListdataType data);
//队列的出队操作
void Quepop(Que* pq);
//队列的判空操作
bool QueEmpty(Que* pq);
//队列的长度
int QueSize(Que* pq);
QListdataType S(Que* pq);//队头
QListdataType Queback(Que* pq);//队尾
void QueInit(Que* pq)
{
assert(pq != NULL);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueEmpty(Que* pq)
{
return pq->size == 0;
}
void QueDestroy(Que* pq)
{
assert(pq != NULL);
Qnode* pcur =pq->phead;
while (pcur)
{
Qnode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->size = 0;
pq->phead = pq->ptail = NULL;
}
void Quepush(Que* pq, QListdataType data)
{
assert(pq!=NULL);
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
if (newnode == NULL)
{
perror("malloc failed");
return;
}
newnode->data = data;
newnode->next = NULL;
if (pq->size==0)//如果一个节点都没有,就需要对头节点也进行更新
pq->phead = pq->ptail = newnode;
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
void Quepop(Que* pq)
{
//这里要保证队列中至少要有一个节点
assert(pq != NULL&&pq->phead!=NULL);
if (pq->size == 1)//如果只有一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
pq->size = 0;
return;
}
Qnode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
pq->size--;
}
int QueSize(Que* pq)
{
return pq->size;
}
QListdataType Quefront(Que* pq)
{
assert(pq != NULL && pq->size != 0);
return pq->phead->data;
}
QListdataType Queback(Que* pq)
{
assert(pq != NULL && pq->size != 0);
return pq->ptail->data;
}
三、扩展:循环队列
除了普通的链式队列,还有一种常见的队列结构叫循环队列。它通常使用数组实现,通过取模运算让数组在逻辑上形成一个环,从而有效利用空间。循环队列常用于生产者消费者模型、网络数据缓冲等场景。
循环队列需要区分队空和队满状态。常用的方法有两种:
-
留一个空位,即
(rear + 1) % capacity == front时认为队满。 -
增加一个计数器记录元素个数。
具体实现可参考相关经典题目,此处不再展开。

结语
栈和队列是数据结构中的基础,理解它们的特性和实现方式对于后续学习更复杂的数据结构(如树、图)以及算法设计(如深度优先搜索、广度优先搜索)至关重要。本文通过C语言完整实现了动态栈和链式队列,希望读者能够自己动手编写代码,加深理解。在实际应用中,可以根据需求选择不同的底层实现,以达到最优的性能。下一篇文章我们将会进行栈帧与队列的OJ题目练习,巩固所学知识。
此外如果想要我的完整代码,请访问我的Github文档,请用加速器否则不一定能打开。
