【初阶数据结构07】——栈与队列的代码实现与解析

文章目录

前言

一、栈

[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文档,请用加速器否则不一定能打开。

相关推荐
We་ct2 小时前
LeetCode 22. 括号生成:DFS回溯解法详解
前端·数据结构·算法·leetcode·typescript·深度优先·回溯
Aaswk3 小时前
蓝桥杯2025年第十六届省赛真题(更新中)
c语言·数据结构·c++·算法·职场和发展·蓝桥杯
Yvonne爱编码3 小时前
JAVA数据结构 DAY7-二叉树
java·开发语言·数据结构
总斯霖4 小时前
P15445永远在一起!题解(月赛T2)
数据结构·c++·算法·深度优先
像污秽一样4 小时前
算法设计与分析-习题4.5
数据结构·算法·排序算法·剪枝
样例过了就是过了4 小时前
LeetCode热题100 全排列
数据结构·c++·算法·leetcode·dfs
xh didida5 小时前
数据结构--实现链式结构二叉树
c语言·数据结构·算法
ab1515175 小时前
3.15二刷基础90、105、106、110
数据结构·c++·算法
白太岁5 小时前
算法:链表:指针变化与环
数据结构·算法·链表