线性表-深入剖析栈与队列(含完整C代码)

本文详细介绍了栈和队列的基本特点,如栈的后进先出(LIFO)和队列的先进先出(FIFO)原则,并通过实例展示了123在栈和队列中的出栈/出队顺序。同时,阐述了栈和队列在物理结构(如CPU中的堆栈)和数据结构中的作用,以及它们在解决计算问题和资源管理中的应用。此外,还提供了顺序栈和循环队列的初始化、入栈/入队、出栈/出队的具体操作步骤,并给出了相关代码实现。
摘要由CSDN通过智能技术生成

目录

💓1.栈与队列的特点

⚽2.思考题

❄️3.栈与队列的作用

3.1物理结构中的栈

3.2数据结构中的栈

 3.3队列的应用

📌4.栈的操作

4.1变量定义

4.2初始化

4.3入栈

4.4出栈

📌5.队列的操作

 5.1初始化

 5.2入队

 5.3出队

🌟6.完整代码

6.1顺序栈

6.2顺序循环队列


💓1.栈与队列的特点

逻辑结构与线性表相同,但运算受到了限制。

栈:限制在表的一端(表尾)进行插入和删除的线性表

队列:限制在表的一端(表尾)进行插入,另一端(表头)进行删除

  •  栈:先进后出、后进先出
  • 队列:先进先出、后进后出

2.思考题

了解了栈和队列的插入删除逻辑,我们不妨来思考一道题。

将数字1、2、3依次分别插入到栈与队列中(在插入123的过程中也可以删除元素),问:123出栈或出队的顺序?

我们先来考虑栈?

注:大家可以盯着格子里看,出现一个元素就是入栈,没了就是在出栈,可以把出栈顺序记住嗷~

123 


 132


 213


 231


 321


 那么出栈顺序一共有5种,312这种情况不可能出现。

那队列呢?

显然只有123这一种情况。

❄️3.栈与队列的作用

3.1物理结构中的栈

在CPU当中,有⼀个非常核心的模块,叫做ALU(算术逻辑单元),也就是执行各种计算和逻辑运算操作的⼀个部件,是我们CPU的执行单元。

比如1 + 1 = 2。如果说,有多个运算参与,比如 111 + 222 + 333,这个时候,他会先计算 111 + 222 ,然后得到⼀个临时结果,再将我们的临时结果和剩下的数字相加。这个时候,我们就需要将临时结果找个地方存⼀下,这个地方,我们叫寄存器,他们的名字就是AX,BX,CX,DX等等。临时结果就保存在寄存器中。

为了实现更复杂的计算,能不能做特别多的寄存器呀?答案是不能,如果做特别多的寄存器,只会增加我们的CPU设计上的成本和复杂性。这个时候,就需要从外面找帮⼿。

这个帮手需要什么条件呢?那就是速度要快。然后,计算机的设计者就将目光放在了内存条上面。 接下来,就要在内存条中划出⼀片专门的区域,用来临时存储数据。既然是专用的,那就需要有个名字。叫做栈。栈其实只是⼀个乳名,实际上这个区域叫做堆栈

要注意,内存里面还有⼀个区域,叫做堆。和栈的特性很不⼀样。所以,栈的本质就是内存中的⼀个区域。他的特殊之处就在于,CPU从中存取数据的方式。就好像弹夹装子弹,就是先⼊后出,后⼊先出。 而CPU中,很多对于数据的操作都要遵循这个规律。在内存中,有⼀个个的存储单元,在存储单元中,就有⼀片区域,就是堆栈。

——摘自《大话数据结构》

3.2数据结构中的栈

数据结构中的栈只是模拟了物理结构中栈的操作方式,是一种抽象的数据结构。

3.3队列的应用

  • 解决主机与外部设备速度不匹配
  • 多⽤户引起的资源竞争问题

📌4.栈的操作

栈也同样分为顺序栈链栈

而栈其实只有两个简单的操作,个人认为用链栈实现的意义不大,还更繁琐,故后文的实现都为顺序栈。

我们借用栈底指针栈顶指针,来控制入栈与出栈的操作。(实际上,只用栈底指针即可实现,需要移动的指针为栈底指针,栈顶指针一直指向栈顶,用于判断栈满,也可以不要它)

4.1变量定义

我采用了全局变量来定义

int *stack; 指针数组

int top;      栈顶指针,不移动,用于判断栈满;也不可不要它

int end;     栈底指针,随着出入栈而移动

4.2初始化

stack = (int*)malloc(Size * sizeof(stack));

top = Size - 1;

end = -1;

  • 此种方法:end是指向栈底元素的。若没有元素,则指向-1,若栈满,则指向top
  • 另一种方法:end指向栈底元素上一个位置。若没有元素,则指向0,若栈满,则指向Size(即top+1)

4.3入栈

若栈未满:

  • 栈底指针上移一个位置
  • 插入元素
void insert(int n)
{
	if (end != top)
	{
		end++;
		stack[end] = n;
		//也可以stack[++end]=n;
	}
	else
	{
		printf("栈已满,入栈失败\n");
	}
}

4.4出栈

若栈不空:栈底指针下移一个位置(释放出栈结点)

void del()
{
	if (end != -1)
	{
		//顺序栈,这里就先只移动了指针
		end--;
		//链栈需要删除结点(free)
	}
	else
	{
		printf("栈空,出栈失败\n");
	}
}

📌5.队列的操作

分类1-存储结构

  • 顺序队列
  • 链队列

注:以下操作的实现为顺序队列。

分类2-实现方式

  • 单向队列
  • 循环队列
  • 双端队列
  1. 循环队列是我们为了解决单向队列出现的一个问题(假溢出)而优化得到的;
  2. 双端队列是将栈和队列的操作融合在一起实现的,STL中的deque就是采用双端队列的思想,我们下一节再讲~

假溢出

  • 队头(front)设置在最近⼀个离开队列元素所占的位置。
  • 队尾(rear)设置在最近⼀个进行入队列的元素位置。
  • 队头和队尾随着插⼊和删除的变化而变化。

当队列为空时,front = rear;但是,这个时候,会有⼀个问题。当我们的队头指针指向size - 1 时,代表长度已满。但是根据队列的规则,就实际情况来说,他还有空闲的空间。那么这个时候,我们就将其称之为假溢出。

为了解决假溢出的问题,我们有两种办法。

  1. 每删除一个数据元素,余下的所有数据元素顺次移动一个位置;
  2. 将我们的顺序队列看成是首位相接的循环结构。

显然第一种方法操作过于繁琐,于是我们来研究第二种方法。

 5.1初始化

两指针都指向0

 5.2入队

  • 队尾指针+1%MAXSIZE
  • 元素插入

 

void insert_queue(int key)
{
	if (isFull())
	{
		printf("队满\n");
	}
	else
	{
		rear = (rear + 1) % maxSize;
		queue[rear] = key;
	}
}

 5.3出队

队首指针+1%MAXSIZE

队列中的元素就是从q.f指向元素到q.r-1指向元素[q.f,q.r)

 

void delete_queue()
{
	if (isEmpty())
	{
		//队空 提示
	}
	else
	{
		front = (front + 1) % maxSize;
	}
}

 此时仍然存在一个问题?

 队空和队满时两指针都是q.f==q.r, 无法根据队首和队尾指针的相对位置判断队列是处于“空” 还是处于“满”的状态。

解决办法

  1.  牺牲一个存储位置,让队空:q.r==q.f ;队满:(q.r+1)%MAXSIZE==q.f
  2. 设一计数器,初始化时计数器清0,入队时,计数器+1,出队时计数器-1
int isFull()
{
	if ((rear+1)%maxSize == front)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int isEmpty()
{
	if (front == rear)//指针相遇 队空
	{
		return 1;
	}
	else {
		return 0;
	}
}

🌟6.完整代码

6.1顺序栈

#include<stdio.h>
#include<stdlib.h>
#define Size 4

//直接采用全局变量
int *stack;//指针数组
int top;//栈顶指针,不移动,用于判断栈满;也不可不要它
int end;//栈底指针,随着出入栈而移动

void Init();
void insert(int n);
void del();
//栈没有其他的操作了
void show();
int main()
{
	Init();
	insert(1);
	insert(2);
	insert(3);
	insert(4);
	insert(5);	
	show();//在此处栈满 从栈顶到栈底依次为4321 
	printf("\n");

	del();//321 
	show();

	return 0;
}
void Init()
{
	stack = (int*)malloc(Size * sizeof(stack));
	top = Size - 1;
	end = -1;
	//此种方法:end是指向栈底元素的。若没有元素,则指向-1,若栈满,则指向top
	//另一种方法:end指向栈底元素上一个位置。若没有元素,则指向0,若栈满,则指向Size(top+1)
}

void insert(int n)
{
	if (end != top)
	{
		end++;
		stack[end] = n;
		//也可以stack[++end]=n;
	}
	else
	{
		printf("栈已满,入栈失败\n");
	}
}

//删除不能指定元素,只能删除栈顶元素
void del()
{
	if (end != -1)
	{
		//顺序栈,这里就先只移动了指针
		end--;
		//链栈需要删除结点(free)
	}
	else
	{
		printf("栈空,出栈失败\n");
	}
}

void show()
{
	for (int i = 0; i <= end; i++)
	{
		printf("%d", stack[i]);
	}
}

6.2顺序循环队列

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

int isEmpty();//队列是否为空
int isFull();//队列是否满
void insert_queue(int);//插入操作
void delete_queue();//删除操作

/*全局变量*/
int* queue;//用数组实现队列
int front;//头指针 因为是数组 这里用下标代表指针
int rear;//尾指针 因为是数组 这里用下标代表指针
int maxSize=5;//当前的容量

int main()
{
	insert_queue(3);
	return 0;
}
int isEmpty()
{
	if (front == rear)//指针相遇 队空
	{
		return 1;
	}
	else {
		return 0;
	}
}
int isFull()
{
	if ((rear+1)%maxSize == front)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
/*
插入操作
int key : 待插入的关键字
*/
void insert_queue(int key)
{
	if (isFull())
	{
		printf("队满\n");// 要么提示 要么扩容
	}
	else
	{
		rear = (rear + 1) % maxSize;
		queue[rear] = key;
	}
}
void delete_queue()
{
	if (isEmpty())
	{
		//队空 提示
	}
	else
	{
		front = (front + 1) % maxSize;
	}
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~在下小吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值