栈和队列

目录

1.栈

1.1 概念与结构

1.2 栈的实现

1.2.1 栈的初始化

1.2.2 栈顶入数据

1.2.3 栈的判空

1.2.4 栈顶出数据

1.2.5 取栈顶元素

1.2.6 获取栈中有效元素个数

1.2.7 栈的销毁

1.3 栈数据结构的代码

2. 队列

2.1 概念与结构

2.2 队列的实现 

2.2.1 队列的初始化

2.2.2 入队列

2.2.3 队列判空

2.2.4 出队列

2.2.5 取队头以及队尾数据

2.2.6  队列有效元素个数

​2.2.7 队列销毁

2.3 队列数据结构的代码

3.栈和队列算法题

3.1 有效的括号

3.2 用队列实现栈

3.3 用栈实现队列

3.4 设计循环队列


1.栈

1.1 概念与结构

栈:是一种线性表,(在逻辑结构上一定连续,在物理结构上不一定连续,取决于底层是循序表还是链表),其只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据在栈顶。

那么我们应该用数组还是用链表来作为栈的底层结构呢?

我们可以看到这三种数据类型的结构,他们插入数据和删除数据的复杂度上分析的话都是没有问题的,三种结构都是可以的,那么既然时间复杂度上看不出来,就从空间复杂度上看,这么这里的双向链表就更吃内存,因为双向链表里面有两个指针,而单链表里面只有一个指针,双链表比单链表多一个指针,那么在32尾机器上会多4个字节,而在64为操作系统上会多八个字节,所以首先排除双向链表。

接下来我们看数组和单链表,数组我们要增容,增容的话我们用到了realloc,realloc原地增容还好,但是如果操作分配的内存不够增容,那么就需要异地增容,就需要开辟新空间,拷贝数据,释放旧空间,这里确实会存在性能的消耗,但是增容我们每次都是成2倍的去增容,所以增容,哪怕是异地增容的操作次数是非常小的,可以忽略不计,也就意味着大多数情况下插入数据我直接往栈顶插入即可,不需要每次都向操作系统去申请空间,而对于单链表来说,每次插入数据的话都需要向操作系统申请一个节点的空间,每次删除都需要释放掉这块空间,更频繁一些,那么就更推荐数组作为栈这个数据结构的底层结构。

1.2 栈的实现

1.2.1 栈的初始化

代码:

//初始化
void STInit(ST* ps)
{
	//不能传空指针
	assert(ps);
	//指向栈的指针置为空
	ps->arr = NULL;
	//内存大小以及有效元素个数
	ps->capacity = ps->top = 0;
}

栈的很多接口和顺序表是一样的。 

测试:

1.2.2 栈顶入数据

对于栈这样的数据结构来说,只能从栈顶出数据以及入数据,不能像顺序表那样做头插,尾插以及指定位置的插入。

代码:

//栈顶入数据
void StackPush(ST* ps, STDataType x)
{
	//不能传空指针
	assert(ps);
	//判断是否需要增容,当空间大小和有效元素个数相等时,就需要增容
	if (ps->capacity == ps->top)
	{
		//计算需要增容多大空间,初始capacity是0,所以要判断
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//增容
		STDataType* newArr = (STDataType*)realloc(ps->arr, sizeof(STDataType) * newCapacity);
		//判断增容是否成功
		if (newArr == NULL)
		{
			//打印错误信息并且退出
			perror("realloc fail!\n");
			exit(1);
		}
		//代码执行到这说明空间增容成功
		ps->capacity = newCapacity;
		ps->arr = newArr;
	}
	//将arr指向的空间的第top位置赋值x,top自增,因为栈顶要往上走
	*(ps->arr + ps->top++) = x;
}

入栈流程:

测试:

 代码执行完初始化,arr指针指向空,空间大小和有效元素个数都为0。

将所有数据插入之后,此时空间大小capacity是8,因为第一次增容4个字节用完,插5的时候不够,增容2倍,变成8,top是有效元素而个数,1!5总共5个有效元素。 

1.2.3 栈的判空

代码:

//判断栈是否为空
//布尔类型要记得加头文件,stdbool.h
bool StackEmpty(ST* ps)
{
	//不能传空指针
	assert(ps);
	//当有效元素个数为0是表示栈为空,返回true
	return ps->top == 0;
}
1.2.4 栈顶出数据

代码:

//栈顶出数据
void StackPop(ST* ps)
{
	//不能传空指针
	assert(ps);
	//栈不能为空
	assert(!StackEmpty(ps));
	--ps->top;
	
}

出栈流程:

测试:

在执行出栈操作之前我们栈中的数据是1~5,有效数据是5个,也就是top是5。

出栈代码执行完成之后,我们的top也有是有效元素格式是4,下面看虽然还是1~5,但是5已经不是有效元素了。 

1.2.5 取栈顶元素

代码:

//取栈顶元素
/*
取栈顶的元素就可以循环打印出栈里面的数据
*/
STDataType StackTop(ST* ps)
{
	//不能传空指针已经栈为u空不能取数据
	assert(ps && !StackEmpty(ps));
	//返回栈中的top减一位置的数据
	return *(ps->arr + ps->top - 1);
}

取栈顶元素流程:

 测试:

我们不能像之前那样写个打印函数,函数里面写个for循环来打印我们的数据结构中的数据,因为栈这种数据结构不能遍历,所有我们只能取栈顶的元素打印出来然后将打印的元素出栈,继续取栈顶的元素,以此类推,直到栈为空。 

如果想要取1,就必须把4,3,2这三个数据出栈,然后取栈顶元素就可以取到1。

注意:栈里的数据不能被遍历,也不能被随机访问。

入栈的顺序是1,2,3,4,出栈的顺序是4,3,2,1,支持栈数据结构的后进先出特性。

1.2.6 获取栈中有效元素个数

代码:

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	//不能传空指针
	assert(ps);
	return ps->top;
}

测试:

出栈前有效元素个数为4个,全部数据出栈,有效元素个数为0。 

1.2.7 栈的销毁

代码:

//销毁
void STDestroy(ST* ps)
{
	//不能传空指针
	assert(ps);
	//判断指向栈的指针是否为空
	if (ps->arr != NULL)
	{
		//销毁动态申请的空间
		free(ps->arr);
		//将该指针置为空指针,防止变为野指针
		ps->arr = NULL;
	}
	//将空间大小和有效元素格式置为0
	ps->capacity = ps->top = 0;
}

测试:

此时,执行销毁之前栈中有5个有效的元素。 

执行销毁之后,arr指针为空,空间大小和有效元素个数都为0。 

1.3 栈数据结构的代码

Elementary data structure: Data structure code and blackboard writing - Gitee.comicon-default.png?t=N7T8https://gitee.com/Axurea/elementary-data-structure/tree/master/2024_7_19_Project

2. 队列

2.1 概念与结构

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

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。

可以取到队尾数据,也可以取到队头数据, 但是队列这样的数据结构是不能遍历的。

入数据在队尾,出数据在队头,那么应该使用数组比较好还是链表比较好呢?

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

队列这样的数据结构该如何去定义呢?

上面我们把链表的头定义为队头,链表的为定义为队尾,那么能不能换一下呢?

2.2 队列的实现 

2.2.1 队列的初始化

代码:

//初始化队列
void QueueInit(Queue* pq)
{
	//不能传空指针
	assert(pq);
	//将phead和ptail都置为空,表示这是一个空的队列
	pq->phead = pq->ptail = NULL;
	//将队列有效元素个数置为0
	pq->size = 0;
}

测试:

断点打到初始化函数之前,此时phead和ptail都为初始值。

 初始化代码执行完成后,phead和ptail指针都为NULL。

2.2.2 入队列

代码:

// 入队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//申请新的节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	//判断malloc的返回值是否为空
	if (newnode == NULL)
	{
		//反正值为空打印错误信息,退出
		perror("malloc error!\n");
		exit(1);
	}
	//为新申请的节点的data赋值
	newnode->data = x;
	//将新申请的节点的next指针置为空
	newnode->next = NULL;
	//处理非空队列和空队列两种情况
	if (pq->phead == NULL)
	{
		//队列为空时新节点既是头节点也是尾节点
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		//队列不为空
		//将新节点插入到ptaild的next位置
		pq->ptail->next = newnode;
		//将ptail指向新的节点,让新的节点变成尾节点
		pq->ptail = newnode;
		//下面这种和上面的效果一样pq->ptail->next位置就是ptail
		//pq->ptail = pq->ptail->next;
	}
    //有效元素个数自增
	pq->size++;
}

入队列流程:

测试:

 第一个数据入队前我们的队列还是空的,ptail和phead都是空指针。

第三个数据插入完成我们可以看到q队列下面的phead和ptail都指向有效的节点。

2.2.3 队列判空

 代码:

//队列判空
//返回类型是布尔类型,一定要加头文件stdbool.h
bool QueueEmpty(Queue* pq)
{
	//不能传空指针
	assert(pq);
	//当头节点为空的时候,就说明队列为空,然后将false取反就为true。
	return !pq->phead;
}
2.2.4 出队列

代码:

// 出队列,队头
void QueuePop(Queue* pq)
{
	//不能传空指针以及队列不能为空
	assert(pq && !QueueEmpty(pq));
	//处理只有一个节点的情况,避免ptail变成野指针
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else//处理有多个节点的情况
	{
		//出队列是从队头出
		//保存要出队的节点
		QueueNode* del = pq->phead;
		//phead指向下一个节点
		pq->phead = pq->phead->next;
		//释放要删除的节点
		free(del);
		del = NULL;
	}
	//有效元素个数自减
	pq->size--;
}

出队列流程:

问题1:

测试:

当我断点打在出队之前,队列中存的三个数据1,2,3。 

当三条出队语句执行完成后,此时队列中的phead指针和ptail指针都为空,说明队列中没有数据。

 如果在继续出队,断言会报错,队列为空不能出数据。

2.2.5 取队头以及队尾数据

代码:

//取队头数据
QDataType QueueFront(Queue* pq)
{
	//不能传空指针以及队列不能为空
	assert(pq && !QueueEmpty(pq));
	//直接返回第一个节点的data数据
	return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	//不能传空指针以及队列不能为空
	assert(pq && !QueueEmpty(pq));
	//直接返回尾节点的data数据
	return pq->ptail->data;
}

测试:

2.2.6  队列有效元素个数

代码:

//队列有效元素个数
int QueueSize(Queue* pq)
{
	//不能传空指针
	assert(pq);
	//返回有效元素个数
	return pq->size;
}

问题1: 

测试:

 2.2.7 队列销毁

代码:

//销毁队列
void QueueDestroy(Queue* pq)
{
	//不能传空指针以及队列不能为空
	assert(pq && !QueueEmpty(pq));
	//遍历队列,释放节点
	QueueNode* pcur = pq->phead;
	//保存第二个节点
	QueueNode* next = pq->phead;
	//pcur为空时说明所以节点释放完成
	while (pcur)
	{
		//保存下一个节点
		next = pcur->next;
		//释放当前节点
		free(pcur);
		//pcur走向下一个节点
		pcur = next;
	}
	//指向队头和队尾的指针置为空
	pq->phead = pq->ptail = NULL;
	//将队列有效元素个数置0
	pq->size = 0;
}

测试:

当我的断点打在销毁函数之前,我们的队列中有1,2,3,4,5,6,6个有效数据。

销毁代码执行完成之后,此时我们的ptail和phead都为空,size也为0。 

2.3 队列数据结构的代码

Elementary data structure: Data structure code and blackboard writing - Gitee.comicon-default.png?t=N7T8https://gitee.com/Axurea/elementary-data-structure/tree/master/2024_7_20_Project

3.栈和队列算法题

3.1 有效的括号

栈和队列算法题 - 有效的括号-CSDN博客文章浏览阅读110次,点赞2次,收藏4次。处理只有一个左括号的问题,左括号入栈,然后字符串遍历完成,然后直接返回true,所i有字符串遍历完成之后我们还要判断栈是否为空,如果不为空,那么就是在遍历字符串的时候没有比较以及出栈。如果只有一个右括号的话说明没有入栈,直接去取栈顶的元素,此时栈是空的,取元素会报断言错误。所有在取栈顶元素之前需要判断一下,栈不为空才能取栈顶的元素。. - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140558415?spm=1001.2014.3001.5501

3.2 用队列实现栈

栈和队列算法题 - 用队列实现栈-CSDN博客文章浏览阅读126次。注意:取栈顶元素的时候不出栈,也有意外这把size-1个元素导入到另一个队列中,就会导致这两个队列都不为空,但是在我们两个队列实现栈的操作中,我们有一个前提就是必须保证至少有一个队列为空。. - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140572503?spm=1001.2014.3001.5501

3.3 用栈实现队列

栈和队列算法题 - 用栈实现队列-CSDN博客文章浏览阅读112次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140587633?spm=1001.2014.3001.5501

3.4 设计循环队列

栈和队列算法题 - 设计循环队列-CSDN博客文章浏览阅读99次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)如何判断队列是满的还是空的?https://blog.csdn.net/m0_74271757/article/details/140589371?spm=1001.2014.3001.5501

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值