【数据结构】栈和队列

本文详细介绍了栈和队列两种数据结构,包括它们的特点和C语言实现。栈采用动态数组实现,遵循先进后出的原则,而队列通过单链表实现,遵循先进先出的原则。文中提供了完整的C语言代码实现,并附带测试用例,帮助读者理解和掌握这两种基础数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

一、栈

1.理解栈

栈的特点

2.实现栈

实现思路

C语言实现

★Stack.h(头文件)代码实现:

★Stack.c(实现功能文件)代码实现:

测试代码:

二、队列

1.理解队列

队列的特点

2.实现队列

 实现思路

C语言实现

★Queue.h(头文件)代码实现:

★Queue.c(队列实现文件)代码实现:

测试代码:


前言

        大家好啊❥(ゝω・✿ฺ)~ 欢迎来到我的数据结构系列文章(〃'▽'〃)~ 

        本文将介绍栈和队列这两个数据结构的特点和实现,实现分别是用数组单链表实现的哦,我们不废话,直接开始吧~

        有需要不想直接看文字可以配合目录直接跳到查看代码区域哦~

一、栈

        栈,数据结构中的一种。此数据结构的操作方式和计算机环环相扣,也是我们学习线性表需要了解到并且实现的一种数据结构。

1.理解栈

        入栈动画演示:

        

         出栈动画演示:

栈的特点

        从上面的动画演示不难总结出: 

 栈数据结构具有的特点是:

        先进后出,后进先出

储存顺序就是按照线性表的尾插即可。

取出顺序即每次都是尾删,然后返回尾部数据。

        那么,我们现在就开启它的实现吧:

2.实现栈

实现思路

        1.首先定义其结构。因为是线性表,所以链表和动态数组均可选择,本篇实现栈选择动态数组进行实现。

        2.结构实现后首先就需要进行初始化。

        3.初始化过后就可以实现压栈和出栈操作了。

        4.最后别忘了将从堆空间申请的空间给释放了。

C语言实现

1.Stack.h

此文件定义结构,声明函数。

        所需要包含的头文件:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

stdio.h

        基本输入输出的函数所含的头文件

assert.h

        使用assert();来进行检测是否为假(对于指针为NULl),如果是就报错

stdlib.h

        方便使用申请堆内存的函数。malloc、realloc......

stdbool.h

        让c语言可以使用布尔类型(true、false)

        当然上面只是包含的头文件,不要忘记了#pragma once防止重复包含定义。

        重定义其数据类型名字--方便对不同的数据进行存储;然后就是一个动态数组基本定义结构了--线性表里面同样的定义方式:一个储存动态数组的指针,一个统计实时数据的变量,一个统计当前数组最大储存数量,方便为之后的扩容做准备

typedef int StackDate; // 定义存储到栈内的数据为栈数据
typedef struct Stack
{
	StackDate* x;// 存放数据的数组
	int top;// 存放实时数据个数
	int capacity;// 容量 当前最大的储存数据量
}Stack;

typedef

        重定义类型--起别名:在定义的结构(枚举、结构体、联合)或者基本数据前,后面跟上新名字,注意;

        然后就是初始化、摧毁、入栈、出栈等函数的实现声明:

//栈的初始化
void StackInit(Stack* pt);
//栈的摧毁
void StackDestroy(Stack* pt);
//入栈
void StackPush(Stack* pt, StackDate x);
//出栈
StackDate StackPop(Stack* pt);
//检测是否存在数据
bool StackDetect(Stack* pt);

因为需要修改里面的x结构(数组),首先不能直接传Stack属性的变量,因为这样是传值拷贝,不会改变原本变量(main)的属性值,所以需要传其地址进行修改。--1级指针。

        综上:

★Stack.h(头文件)代码实现:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1

/*<栈> 使用数组实现(也可以使用链表进行实现)
* 实现基本功能:1.入栈 2.出栈 上述两者遵循先入后出,后进先出原则 
*3.栈的摧毁 
*/
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int StackDate; // 定义存储到栈内的数据为栈数据
typedef struct Stack
{
	StackDate* x;// 存放数据的数组
	int top;// 存放实时数据个数
	int capacity;// 容量 当前最大的储存数据量
}Stack;

//栈的初始化
void StackInit(Stack* pt);
//栈的摧毁
void StackDestroy(Stack* pt);
//入栈
void StackPush(Stack* pt, StackDate x);
//出栈
StackDate StackPop(Stack* pt);
//检测是否存在数据
bool StackDetect(Stack* pt);

注意#define _CRT_SECURE_NO_WARNINGS 1,此是我使用vs2019为了能使用printf和scanf函数所包含的,如果不包含会被迫使用优化后的函数哦~

2.Stack.c

此文件实现栈的核心代码。

        栈的初始化

//初始化栈  置空
void StackInit(Stack* pt)
{
	assert(pt);//防止造成空指针引用
	pt->x = NULL;
	pt->top = pt->capacity = 0;
}

        栈的摧毁

//栈的摧毁 对于在堆内存申请空间的数组进行回收
void StackDestroy(Stack* pt)
{
	assert(pt);
	free(pt->x);
	pt->x = NULL;
	pt->top = pt->capacity = 0;
}

注意free后把实时数据和容量也置为空哦~

        入栈

//入栈 常规线性表那样进行尾插
void StackPush(Stack* pt, StackDate x)
{
	assert(pt);
	//如果数组为空或者已经满了
	if (pt->top == pt->capacity)
	{
		int newquantity = (pt->capacity == 0) ? 4 : (pt->capacity) * 2;//三目运算符确定是否空还是满状态
		StackDate* temp = (StackDate*)realloc(pt->x, sizeof(StackDate) * newquantity);//重新开辟或者扩展内存
		if (temp == NULL)//没有申请到内存
		{
			perror("StackPush");//报出此处函数的问题。
			exit(-1);//结束进程 0正常结束,除此之外其他数为不正常结束
		}
		pt->x = temp;
		pt->capacity = newquantity;
	}
	//正常插入
	pt->x[pt->top] = x;
	pt->top++;
}

 需要注意判断是否第一次进入(空数组)和满数组有一个共同特点--容量和实时数据相等,利用这个进行区分。后面正常插入是公共代码,不管是否相等都要执行的。

        出栈

//出栈 弹出数据的拷贝值,然后栈内弹出数据
StackDate StackPop(Stack* pt)
{
	assert(pt&&(!StackDetect(pt)));
	return pt->x[--pt->top];
}

注意这里是出数据的同时进行弹出数据(删除),所谓的删除只是把实时数据变量往后进行移动,这样新加的时候直接覆盖掉原本数据即可。StackDetect是后面要实现的函数,如果数据为空就返回真,所以需要加上一个逻辑反进行限制,防止没有数据造成数组越界。

        检测是否存在数据

//检测是否存在数据 判断top是否为0
bool StackDetect(Stack* pt)
{
	assert(pt);
	return pt->top == 0;//返回true 表示没有数据了
}

这里的return可以直接这样写,需要熟练一点。不熟练的话可以使用if结构进行判断返回true或者false。

        综上:

★Stack.c(实现功能文件)代码实现:

#include"Stack.h"
//栈功能实现区:

//初始化栈  置空
void StackInit(Stack* pt)
{
	assert(pt);//防止造成空指针引用
	pt->x = NULL;
	pt->top = pt->capacity = 0;
}

//栈的摧毁 对于在堆内存申请空间的数组进行回收
void StackDestroy(Stack* pt)
{
	assert(pt);
	free(pt->x);
	pt->x = NULL;
	pt->top = pt->capacity = 0;
}

//入栈 常规线性表那样进行尾插
void StackPush(Stack* pt, StackDate x)
{
	assert(pt);
	//如果数组为空或者已经满了
	if (pt->top == pt->capacity)
	{
		int newquantity = (pt->capacity == 0) ? 4 : (pt->capacity) * 2;//三目运算符确定是否空还是满状态
		StackDate* temp = (StackDate*)realloc(pt->x, sizeof(StackDate) * newquantity);//重新开辟或者扩展内存
		if (temp == NULL)//没有申请到内存
		{
			perror("StackPush");//报出此处函数的问题。
			exit(-1);//结束进程 0正常结束,除此之外其他数为不正常结束
		}
		pt->x = temp;
		pt->capacity = newquantity;
	}
	//正常插入
	pt->x[pt->top] = x;
	pt->top++;
}

//出栈 弹出数据的拷贝值,然后栈内弹出数据
StackDate StackPop(Stack* pt)
{
	assert(pt&&(!StackDetect(pt)));
	return pt->x[--pt->top];
}

//检测是否存在数据 判断top是否为0
bool StackDetect(Stack* pt)
{
	assert(pt);
	return pt->top == 0;//返回true 表示没有数据了
}

        然后就是测试环节了,大家可以随便测试,若有问题欢迎进行讨论指出!谢谢,下面是我的测试代码:

测试代码:

#include"Stack.h"

void test()
{
	Stack a;
	StackInit(&a);

	//测试入栈
	StackPush(&a, 1);
	StackPush(&a, 2);
	StackPush(&a, 3);
	StackPush(&a, 4);
	StackPush(&a, 5);
	//测试出栈
	StackDate x[5] = { 0 };
	for (int i = 0; i < 5; i++)
	{
		x[i] = StackPop(&a);
		printf("%d ", x[i]);
	}
	printf("\n");
	//StackDate x6 = StackPop(&a);

	StackDestroy(&a);
}

int main()
{
	//主进程
	test();//测试函数
	return 0;
}

        后序相关栈的实现我还会补充的!我们一起努力学习吧~

二、队列

        队列,同样是数据结构之一,但是其特性和栈有许多相反之处。

1.理解队列

        入队列动画演示:

         出队列动画演示:

队列的特点

 由上述动图演示不难看出队列的特点:

        先进先出,后进后出。果然和栈的特性完全相反

储存顺序同样是线性表的普通尾插。

取出顺序就是线性表的头部数据以及头删了。

2.实现队列

 实现思路

        1.由于队列的特性,结构选择链表会相对好一些,因为设计到头删,数组不好进行头删。

        2.结构由单链表决定,但是为了保证头删和取出头部数据,在单独建立一个结构用来存放链表头地址和尾地址。

        3.初始化将头和尾指针置为空

        4.实现入队列和出队列操作。出队列注意判断头指针和尾指针指向同一位置。

        5.最后别忘了摧毁回收内存。

C语言实现

1.Queue.h

此文件定义结构,声明函数。

        首先声明的头文件:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

(详细细节可以查看栈实现的头文件相关介绍)

        重定义储存数据类型(默认int)和单链表结构:

typedef int QueueDate;

//下面定义链表结构 -- 单链表 单链表的话需要单独定义两个指针一个指向头一个指向尾,方便进行插入和出列
typedef struct QListNode
{
	QueueDate x;
	struct QListNode* next;//存放下一指针的
}QNode;

单链表结构,一个储存数据,一个储存下一个节点地址,但是仅仅这样是拿不到头节点的,所以需要定义下面的结构;

        定义存放队列链表的头节点和尾结点的结构:

typedef struct Queue
{
	QNode* head;//存放队首
	QNode* tail;//存放队尾
}Queue;

        初始化、摧毁、入队、出队...函数声明:

//队列初始化
void QueueInit(Queue* pq);
//队列摧毁
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QueueDate x);
//出队
QueueDate QueuePop(Queue* pq);
//判断是否存在元素:
bool QueueEmpty(Queue* pq);

        综上:

★Queue.h(头文件)代码实现:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

/*<队列> 实现:链表
* 1.入列 2.出列 3.摧毁列表  上述遵循队列的先进先出原则
*/

typedef int QueueDate;

//下面定义链表结构 -- 单链表 单链表的话需要单独定义两个指针一个指向头一个指向尾,方便进行插入和出列
typedef struct QListNode
{
	QueueDate x;
	struct QListNode* next;//存放下一指针的
}QNode;

typedef struct Queue
{
	QNode* head;//存放队首
	QNode* tail;//存放队尾
}Queue;

//队列初始化
void QueueInit(Queue* pq);
//队列摧毁
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QueueDate x);
//出队
QueueDate QueuePop(Queue* pq);
//判断是否存在元素:
bool QueueEmpty(Queue* pq);

 注意#define _CRT_SECURE_NO_WARNINGS 1的细节可以了解上面的Stack.h文件里面的介绍。

2.Queue.c

队列实现的核心代码文件。

        队列初始化:

//队列初始化,让队列的头和尾指向空
void QueueInit(Queue* pq)
{
	assert(pq);//防止空指针引用
	pq->head = pq->tail = NULL;
}

        队列摧毁:

//队列摧毁 将堆内存申请的空间回收
void QueueDestroy(Queue* pq)
{
	assert(pq);//查看是否空指针引用
	while (pq->head)
	{
		QNode* temp = pq->head;
		pq->head = temp->next;
		free(temp);
	}
	pq->tail = NULL;
}

注意此时要摧毁的是链表,可不只是把头和尾空间释放掉,因为有可能出现两个以上啊,所以需要循环遍历,把链表的每一个节点空间都释放掉,然后将头和尾置为空即可。

        入队:

//入队 普通单链表进行尾插
void QueuePush(Queue* pq, QueueDate x)
{
	assert(pq);
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL)
	{
		perror("QueuePush");
		exit(-1);//不正常情况退出进程
	}
	newNode->x = x;
	newNode->next = NULL;
	if (pq->head == NULL)//第一次进
	{
		pq->head = pq->tail = newNode;
	}
	else//正常入队
	{
		pq->tail->next = newNode;
		pq->tail = newNode;//保存尾元素
	}
}

正常单链表进行尾插,注意区分是否第一次进即可,然后时刻记住tail是保持尾结点的地址的。

        出队:

//出队 先进先出出头节点,然后将头节点更换成下一个节点
QueueDate QueuePop(Queue* pq)
{
	assert(pq && !QueueEmpty(pq));//判断是否空指针引用或者是否空队列
	QueueDate x = pq->head->x;
	if (pq->head == pq->tail)//只有一个元素
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* temp = pq->head->next;
		free(pq->head);
		pq->head = temp;
	}
	return x;
}

出队可不能像数组实现的那样简简单单覆盖就好了,此时这个单独的堆空间需要被回收,当然,此时要注意是否头和尾指向同一处(空或者只有一个数据),此时头改变尾也要改变。

        判断是否存在元素:

//判断是否存在元素
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;//是空表明没有元素,返回真,否则为假
}

        综上:

★Queue.c(队列实现文件)代码实现:

#include"Queue.h"
//队列实现区

//队列初始化,让队列的头和尾指向空
void QueueInit(Queue* pq)
{
	assert(pq);//防止空指针引用
	pq->head = pq->tail = NULL;
}

//队列摧毁 将堆内存申请的空间回收
void QueueDestroy(Queue* pq)
{
	assert(pq);//查看是否空指针引用
	while (pq->head)
	{
		QNode* temp = pq->head;
		pq->head = temp->next;
		free(temp);
	}
	pq->tail = NULL;
}

//入队 普通单链表进行尾插
void QueuePush(Queue* pq, QueueDate x)
{
	assert(pq);
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL)
	{
		perror("QueuePush");
		exit(-1);//不正常情况退出进程
	}
	newNode->x = x;
	newNode->next = NULL;
	if (pq->head == NULL)//第一次进
	{
		pq->head = pq->tail = newNode;
	}
	else//正常入队
	{
		pq->tail->next = newNode;
		pq->tail = newNode;//保存尾元素
	}
}

//出队 先进先出出头节点,然后将头节点更换成下一个节点
QueueDate QueuePop(Queue* pq)
{
	assert(pq && !QueueEmpty(pq));//判断是否空指针引用或者是否空队列
	QueueDate x = pq->head->x;
	if (pq->head == pq->tail)//只有一个元素
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* temp = pq->head->next;
		free(pq->head);
		pq->head = temp;
	}
	return x;
}


//判断是否存在元素
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;//是空表明没有元素,返回真,否则为假
}

           然后就是测试环节了,大家可以随便测试,若有问题欢迎进行讨论指出!谢谢,下面是我的测试代码:

测试代码:

#include"Queue.h"
//队列测试区

void test()
{
	Queue q;
	QueueInit(&q);

	//测试入队
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 4);
	//出队测试
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueuePop(&q));
	}
	printf("\n");
	QueueDestroy(&q);
}

int main()
{
	test();//测试代码
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值