【数据结构】栈和队列的表示与实现(C语言)

栈和队列是两种重要的线性结构。从数据结构的角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,他们都是操作受限的线性表(只能从两端操作,不可以在中间进行插入或删除操作)。

1.栈

1.1栈的概念和结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶

在这里插入图片描述
在这里插入图片描述

1.2 栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

在这里插入图片描述
在这里插入图片描述

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int StackDataType;
#define N 10
typedef struct Stack
{
	StackDataType a[N];
	int top; // 栈顶
}ST;
// 支持动态增长的栈
typedef int StackDataType;
typedef struct Stack
{
	StackDataType* a;
	int capacity;
	int top;
}ST;
// 初始化栈
void StackInit(ST* ps);
// 入栈
void StackPush(ST* ps, StackDataType x);
// 出栈
void StackPop(ST* ps);
// 获取栈顶元素
StackDataType StackTop(ST* ps);
// 获取栈中有效元素个数
int StackSize(ST* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps);
// 销毁栈
void StackDestroy(ST* ps);

1.2.1 初始化栈

栈初始化的是栈的初始状态,你可以一开始就malloc一些空间,等栈满需要插入的时候再增容,也可以一开始不直接malloc空间,插入的时候再开辟空间,这个随大家的心情,没有唯一的框架。
我们这里选择的是第一种。需要注意的是,我们在做OJ题的时候使用malloc可以不判断是否malloc成功,因为一般不存在失败的时候,但是我们在自己写代码的时候,最好加上判断,我们在写工程的时候,可能会存在内存不足的时候,保持这样一个好习惯。

代码如下:

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (StackDataType*)malloc(sizeof(StackDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = 4;
}

1.2.2 入栈

入栈操作很简单,因为我们是使用顺序表实现的,直接尾插就可以了,唯一需要注意的是,如果栈满了,则需要扩容。
这里我们在实现的时候,其实有两种情况,就是ps->top指针指向的是栈顶的位置,还是栈顶的下一个位置。我们这里是指向栈顶的下一个位置,如果是指向栈顶的位置,实现其他接口的时候,代码是需要修改的,具体的情况随你自己的代码而定。

在这里插入图片描述

代码如下:

void StackPush(ST* ps, StackDataType x)
{
	assert(ps);
	//扩容
	if (ps->top == ps->capacity)
	{
		StackDataType* tmp = (StackDataType*)realloc(ps->a, sizeof(StackDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}

	ps->a[ps->top++] = x;
}

1.2.3 出栈

出栈操作同样简单,他不需要考虑是否需要扩容,直接ps->top指针减一即可,这里大家可能会有一个疑问,就是需不需要将原来栈顶的值置0,其实是没有必要的,有两个原因,首先,他可能本来的值就是0,再者就是,我们只访问到栈顶的元素,至于他的下一个元素是什么我们根本不关心,也取不到他的值,不影响我们接下来的所有操作,比如插入操作。
在这里插入图片描述

代码如下:

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//判空,如果栈为空,不能进行出栈操作

	ps->top--;
}

1.2.4 获取栈顶元素

取栈顶元素,看我们ps->top指针具体指向的位置,我们这里指向的是栈顶的下一个元素,所以我们在获取栈顶元素的时候需要返回ps->top指针的前一个位置。
这里大家可能还有一个问题,这么简单的一句代码,还有没有必要将其封装起来,回答是有必要的,因为我们是代码编写者,我们知道代码的逻辑,但是使用的人他们并不知道,如果需要使用的时候,还需要将我们写的代码读懂,这太麻烦了,所以即使是简单的一句代码,我们还是需要将其封装起来的。

代码如下:

StackDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top-1];
}

1.2.5 获取栈中有效元素个数

获取栈中有效元素的个数,我们直接可以返回ps->top指向的顺序表的下标,因为我们ps->top指针指向的就是栈顶的下一个位置,就是栈中元素的个数。
如果实现的时候,ps->top指针指向的是栈顶的位置,则需要返回ps->top+1;

代码如下:

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

1.2.6 检测栈是否为空

判空,我们可以直接判断ps->top是否等于0,等于0的时候,返回true,不等于0的时候,说明栈不为空,返回false。

代码如下:

bool StackEmpty(ST* ps)
{
	assert(ps);
	
	return ps->top == 0;
}

1.2.7 销毁栈

销毁栈的时候,我们需要将栈开辟的空间销毁,将ps->top和ps->capacity置为0。

代码如下:

void StackDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->capacity = 0;
	ps->top = 0;
}

1.3 原码

链接: 栈代码实现的Gitee仓库

2. 队列

2.1 队列的概念和结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

在这里插入图片描述

2.2 队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
在这里插入图片描述

typedef int QLDataType;

// 链式结构:表示队列
typedef struct QueueListNode
{
	QLDataType data;
	struct QueueListNode* next;
}QNode;

// 队列的结构
typedef struct Queue
{
	QNode* front;//队头
	QNode* tail;//队尾
	int size;
}Queue;

// 初始化队列
void QueueInit(Queue* q);

// 队尾入队列
void QueuePush(Queue* q, QLDataType x);

// 队头出队列
void QueuePop(Queue* q);

// 获取队列头部元素
QLDataType QueueFront(Queue* q);

// 获取队列队尾元素
QLDataType QueueBack(Queue* q);

// 获取队列中有效元素个数
int QueueSize(Queue* q);

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q);

// 销毁队列
void QueueDestroy(Queue* q);

2.2.1 初始化队列

队列的初始化,我们选择的是使用链式结构存储队列,并且定义了两个指针,分别指向队头和对尾,方便我们进行插入和删除操作,队头进行出队操作,队尾进行入队操作。
当队列为空的时候,可以将这两个指针都置空,ps->size置0。

代码如下:

void QueueInit(Queue* q)
{
	assert(q);

	q->front = q->tail = NULL;
	q->size = 0;
}

2.2.2 队尾入队列

队尾入队列,首先我们需要开辟一个新的节点,将新节点的值置为x(我们想要插入的值),将新节点的next指针置空。
在入队的时候,我们需要判断,是否是第一次入队,第一次入队的时候,需要将新节点赋值给队头指针和队尾指针,如果不是第一次入队,则只需要将新节点赋给队尾指针的指针域,再将新节点赋给队尾指针,让新节点成为新的队尾。

代码如下:

void QueuePush(Queue* q, QLDataType x)
{
	assert(q);
	
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (!newnode)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	if (q->front == NULL)
	{
		q->front = q->tail = newnode;
		q->size++;
	}
	else
	{
		q->tail->next = newnode;
		q->tail = newnode;
		q->size++;
	}
}

2.2.3 队头出队列

队头出队列,此时我们需要判断两种情况,当队列只有一个结点的时候和当队列有多个结点的时候,当队列只有一个结点的时候,队列的头指针和为指针指向那同一个位置,我们需要先将此空间释放,再将头指针和尾指针都置空。当队列有多个结点的时候,我们可以先记录下头指针的下一个位置,然后将头结点释放,再让头指针指向记录的那个位置,也可以用一个指针先指向头结点的位置,然后让头指针指向下一个结点,再释放记录的头结点。

代码如下:

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	if (q->front == q->tail)
	{
		free(q->front);
		q->front = q->tail = NULL;
		q->size--;
	}
	else
	{
		QNode* cur = q->front;
		q->front = q->front->next;
		free(cur);
		q->size--;
	}
}

2.2.4 获取队列头部元素

获取队列头部元素,这个很简单,直接返回头指针指向结点的数据域就可以了。

代码如下:

QLDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->front->data;
}

2.2.5 获取队列队尾元素

获取队列队尾元素,这个同上,返回尾指针指向结点的数据域即可。

代码如下:

QLDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->tail->data;
}

2.2.6 获取队列中有效元素个数

获取队列中有效元素个数,这个也同上,我们在定义的时候,就定义了size(队列中元素的个数),直接返回即可。

代码如下:

int QueueSize(Queue* q)
{
	assert(q);

	return q->size;
}

2.2.7 检测队列是否为空

检测队列是否为空,我们可以判断头指针和尾指针是否为空,为空则说明队列为空,返回true,不为空则说明队列不为空,返回false。

代码如下:

bool QueueEmpty(Queue* q)
{
	assert(q);
	
	return q->front == NULL && q->tail == NULL;
}

2.2.8 销毁队列

销毁队列,销毁队列比较麻烦,我们需要将队列中的节点,一个一个的释放,否则会造成内存的泄露。最后将头指针和尾指针都置空,再将size置为0。

代码如下:

void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->front;
	while (cur)
	{
		QNode* del = cur->next;
		free(cur);
		cur = del;
	}
	q->front = q->tail = NULL;
	q->size = 0;
}

2.3 队列的原码

链接: 队列原码的Gitee仓库


结语:
感谢大家看到这里,如有错误,希望各位大佬多多指正!
祝大家心情愉悦!
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值