C语言栈和队列的实现

栈(Stack)是一种操作受限的线性数据结构,它的核心规则是先进后出(FILO, First In Last Out)最先放入栈的元素,最后才能取出;最后放入的元素,最先能取出

比如:叠盘子:新盘子总是叠在最上面(入栈),取盘子也只能从最上面拿(出栈),不能从中间或底部抽,这就是栈的核心逻辑

栈的操作范围被严格限制在栈顶(Stack Top)只有栈顶的元素能被读取或删除,栈底(Stack Bottom)的元素只能等上面所有元素都取出后才能操作

栈可以用很多种方式实现,我们来用较为简单的方法实现,动态顺序表,也就是动态数组

复制代码
typedef int STDataType;
typedef struct stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

//首先要创建一个结构体

初始化

复制代码
void STInit(ST* pst);

//初始化
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
	//top=-1说明这是一个栈顶元素的当前位置
}

pst->a 是栈结构体中用于存储栈元素的动态数组指针(通常定义为 int* a 或其他数据类型指针);

赋值为 NULL 表示:栈初始化时没有分配任何内存空间,此时栈里没有任何元素,避免野指针(指向无效内存的指针)风险

pst->top:表示栈顶指针(核心变量),初始化为 0,这里体现了栈的一种常见设计规则:

当 top=0 时,top 指向栈顶下一个可插入元素的位置(空栈状态下,第一个元素会插入到下标 0 的位置)

销毁

复制代码
void STDestroy(ST* pst);

//销毁
void STDestroy(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

pst->a 是栈结构体中用于存储栈元素的动态数组指针(通常定义为 int* a 或其他数据类型指针);

赋值为 NULL 表示:栈初始化时没有分配任何内存空间,此时栈里没有任何元素,避免野指针(指向无效内存的指针)风险

pst->top:表示栈顶指针(核心变量),初始化为 0,这里体现了栈的一种常见设计规则:

当 top=0 时,top 指向栈顶下一个可插入元素的位置(空栈状态下,第一个元素会插入到下标 0 的位置)

入栈

复制代码
void STPush(ST* pst, STDataType x);

//入栈
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	//扩容
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		else
		{
			pst->a = tmp;
			pst->capacity = newcapacity;
		}
	}
	pst->a[pst->top] = x;
	pst->top++;
}

功能:将元素 x 压入栈顶,栈容量不足时自动动态扩容;

核心逻辑:

先校验栈指针有效性(assert),避免空指针操作;

若栈顶指针 top 等于容量 capacity(栈满),触发扩容:首次扩容为 4 个元素空间,后续扩容为原容量 2 倍;

用 realloc 调整内存(临时指针接收结果,防止扩容失败丢失原内存),扩容失败则打印错误并退出;

扩容完成 / 栈未满时,将元素存入 top 指向的位置,top 后移一位(指向新插入位);

关键设计:2 倍扩容兼顾效率,临时指针接收 realloc 结果避免内存泄漏

出栈

复制代码
void STPop(ST* pst);

void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}

pst->capacity:表示栈的容量(即栈最多能存储的元素个数),初始化为 0,说明栈一开始没有可存储元素的空间;

功能:逻辑上移除栈顶元素(栈遵循 "后进先出",仅移动 top 指针,无需主动清理元素值);

双重校验:

assert(pst):确保传入的栈指针非空,避免空指针解引用崩溃;

assert(pst->top > 0):确保栈内有元素(top>0 表示栈非空),防止空栈执行出栈操作;

核心操作:pst->top-- 让栈顶指针前移一位,原栈顶元素不再被访问,即完成出栈

取出栈顶数据

复制代码
STDataType STTop(ST* pst);

//取出栈顶的数据
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->a[pst->top - 1];

}

功能:读取并返回栈顶元素的值,仅查询不修改栈的结构(栈顶指针 top 不变);

双重校验:

assert(pst):确保栈指针非空,避免空指针解引用;

assert(pst->top > 0):确保栈内有元素,防止访问空栈的无效内存;

核心逻辑:因栈设计中 top 指向 "栈顶下一个可插入位置",所以栈顶元素的下标是top - 1,直接返回该位置的值即可。

判空

复制代码
bool STEmpty(ST* pst);

//判空
bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}

返回值类型是布尔型,这里需要包含一个头文件<stdbool.h>

功能:判断栈是否为空,返回 bool 类型结果(需包含<stdbool.h>头文件);

校验逻辑:assert(pst)确保传入的栈指针非空,避免空指针解引用崩溃;

判空依据:因栈设计中top指向栈顶下一个可插入位置,top=0表示栈内无任何元素,即栈为空

获取数据个数

复制代码
int STSize(ST* pst);

//获取数据个数
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;

}

功能:查询并返回栈内有效元素的总个数,仅读不修改栈;

校验逻辑:assert(pst)确保传入指针非空,避免程序崩溃;

计数依据:因栈的top指向栈顶下一个可插入位置,top的数值恰好等于栈内已存元素数(如 top=3 表示栈内有 3 个元素)

现在打印输出看一下效果

复制代码
fun1()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}
	STDestroy(&s);
}
int main()
{
	fun1();
	return 0;
}

队列

队列是一种线性数据结构,其核心遵循 先进先出(First In First Out,简称 FIFO) 的访问原则

比如:排队买票、食堂打饭、打印机的任务队列,先到达的元素,先被处理;后到达的元素,只能排在队尾等待

注:只能在队列的一端插入元素,在另一端删除元素

复制代码
typedef int DataType;
typedef struct Linked
{
	DataType data;
	struct Linked* pnext;
}List;
typedef struct NodeQueue
{
	List* phead;
	List* ptail;
	int size;
}Queue;

这里为什么要定义两个结构体呢?

第一个结构体仅负责存储队列中的单个数据元素,以及通过next指针和其他节点建立链接,是链式队列的最小数据单元,就像排队时的 "每个人",只承载自己的信息,并知道自己后面是谁

第二个结构体不存储具体数据,而是管理整个队列的核心信息,是操作队列的 "总控入口"

初始化

复制代码
void QueueInit(Queue* ps);

void QueueInit(Queue* ps)
{
	assert(ps);
	ps->phead = ps->ptail = NULL;
	ps->size = 0;
}

销毁

复制代码
void QueueDestroy(Queue* ps);

void QueueDestroy(Queue* ps)
{
	assert(ps);
	List* newphead = ps->phead;
	while (newphead)
	{
		List* cur = newphead->pnext;
		free(newphead);
		newphead = cur;
	}
	ps->phead = ps->ptail = NULL;
	ps->size = 0;

}

这里的销毁其实和单链表的销毁差不多

尾入

复制代码
void QueuePush(Queue* ps, DataType x);

void QueuePush(Queue* ps, DataType x)
{
	assert(ps);
		List* space = (List*)malloc(sizeof(List));
		if (space == NULL)
		{
			perror("malloc");
			return;
		}
		else 
		{
			space->pnext = NULL;
			space->data = x;
		}
		if (ps->ptail == NULL)
		{
			ps->phead = ps->ptail = space;
		}
		else
		{
			ps->ptail->pnext = space;
			ps->ptail = space;
		}
		ps->size++;
		
}

上一章接提到过,单链表的节点不需要扩容,每一个节点都是固定的大小

功能:基于链表实现队列入队,将元素 x 插入队列尾部(队列遵循 "先进先出");

核心逻辑:

校验队列指针有效性,避免空指针操作;

申请新链表节点并初始化(存值 x、后继置空),内存申请失败则报错退出;

空队列:头尾指针均指向新节点;非空队列:尾节点后继指向新节点,尾指针移至新节点;

队列元素个数 size 自增,记录有效元素数;

设计特点:链表实现队列,入队仅操作尾指针,时间复杂度 O (1),效率高

头出

复制代码
void QueuePop(Queue* ps);

void QueuePop(Queue* ps)
{
	assert(ps);
	if (ps->phead->pnext == NULL)
	{
		free(ps->phead);
		ps->phead = ps->ptail = NULL;
	}
	else
	{
		List* newphead = ps->phead->pnext;
		free(ps->phead);
		ps->phead = newphead;//ps->phead->pnext错误,这里的phead已经被释放掉了
	}
	ps->size--;
}

功能:基于链表实现队列出队,移除队列头部元素(队列遵循 "先进先出"),释放节点内存并更新指针 / 元素个数;

核心逻辑:

校验队列指针有效性,避免空指针操作;

分两种场景处理:

队列仅 1 个节点:释放该节点,头尾指针均置空(防止野指针);

队列多节点:先保存原头节点的下一个节点(新头),再释放原头节点,最后更新头指针;

关键注释提示:不能直接用ps->phead->pnext赋值,因原头节点已被free,指针失效;

队列元素个数size自减,同步更新数量;

判断是否为空

复制代码
bool QueueEmpty(Queue* ps);

bool QueueEmpty(Queue* ps)
{
	assert(ps);
	return ps->size == 0;
}

取对头和对尾

复制代码
DataType QueueFront(Queue* ps);
DataType QueueBack(Queue* ps);

DataType QueueFront(Queue* ps)
{
	assert(ps);
	assert( !QueueEmpty(ps));
	return ps->phead->data;

}
DataType QueueBack(Queue* ps)
{
	assert(ps);
	assert(ps->ptail);
	return ps->ptail->data;
}

功能区分:

QueueFront:查询并返回队列头部元素(队列 "先进先出",头部是最先入队的元素);

QueueBack:查询并返回队列尾部元素(尾部是最后入队的元素);

校验逻辑:

两个函数均先校验队列指针ps非空,避免空指针解引用;

QueueFront通过!QueueEmpty(ps)校验队列非空;QueueBack直接校验ps->ptail非空(等价于队列非空),两种写法均为防止访问空队列的无效节点;

核心操作:仅返回对应指针(phead/ptail)指向节点的data值,不修改队列任何结构(指针、size 均不变)

取出队列个数

复制代码
DataType QueueSize(Queue* ps);

DataType QueueSize(Queue* ps)
{
	assert(ps);
	return ps->size;
}

看一下最终运行结果

复制代码
func1()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 4);
	QueuePush(&q, 3);
	QueuePush(&q, 2);
	QueuePush(&q, 1);
	while (!QueueEmpty(&q))
	{
		printf("%d ",QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestroy(&q);
}
int main()
{
	func1();
}
相关推荐
猷咪20 分钟前
C++基础
开发语言·c++
IT·小灰灰22 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧23 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q24 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳024 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾24 分钟前
php 对接deepseek
android·开发语言·php
2601_9498683628 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
飞机和胖和黄40 分钟前
考研之王道C语言第三周
c语言·数据结构·考研
星火开发设计42 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
qq_177767371 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos