数据结构——线性表之 栈 和 队列,详细解析

可以先了解线性表的无头单向非循环链表(单链表)---数据结构——单链表的增加、删除、查找、修改,详细解析_昵称就是昵称吧的博客-CSDN博客,和线性表的带头双向循环链表---数据结构:带头双向循环链表——增加、删除、查找、修改,详细解析_昵称就是昵称吧的博客-CSDN博客


目录

一、线性表之栈

1、栈的表示和实现

1.1栈的概念及结构

1.2栈的实现表示方法

2、设计栈的功能实现

2.1栈空间通过动态存储数据

2.2栈空间的初始化

2.3栈空间的销毁

2.4入栈的功能

2.5打印栈里数据的功能

2.6出栈的功能

2.7返回栈顶的数据

2.8返回栈里的数据的个数

2.9判断栈里是否有数据

3、总代码

4、 代码运行实例

二、线性表之队列

1、栈的表示和实现 

1.1队列的概念和结构

 1.2队列的实现表示方法

2、设计队列的功能实现

2.1单链表节点的设计

2.2队列的特殊处理

2.3初始化指针head和指针tail

2.4队尾入(尾插) 

2.5对头出(头删)

2.6返回对头的数据(链表里的第一个节点的数据)

2.7返回队尾的数据(链表里的尾节点的数据)

2.8返回队列(链表)里的数据个数

2.9判断链表里是否有元素

2.10队列空间的销毁

3、总代码

4、代码运行实例


一、线性表之栈

1、栈的表示和实现

1.1栈的概念及结构

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

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

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

可以将栈理解为弹夹,压栈就是压入子弹,出栈就是打出子弹。

结构如下所示: 


1.2栈的实现表示方法

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


2、设计栈的功能实现

2.1栈空间通过动态存储数据

typedef int StackDataType;//重定义类型名

typedef struct Stack
{
	StackDataType* a;//创建一个指针,用来接收数组首元素地址,数组里面的类型是StackDataType
	                 //用数组来描述栈,因为出栈和入栈都是一个地方
	int top;//相当于数组的下标,可以是0或-1,这里我们选择0
	int capacity;//数组的容量
}ST;

结构体里用一个指针接收动态开辟的内存空间的地址。 


2.2栈空间的初始化

//初始化结构体,将结构体变量的地址传递过来,是地址传递,这样就能改变实参的内容了
void StackInit(ST* ps)
{
	assert(ps);//地址传递,断言好习惯

	//动态开辟一个数组的空间,数组的元素类型是StackDataType,将首元素地址赋给指针a
	ps->a = (StackDataType*)malloc(sizeof(StackDataType) * 4);
	if (ps->a == NULL)//判断是否成功开辟动态空间
	{
		perror("the mistake is");
	}

	ps->top = 0;        //相当于数组下标,可以初始化0或-1,这里用0
	                    //用0数组下标从0开始,用-1数组下标从-1开始

	ps->capacity = 4;   //从开辟的动态空间可以看出数组的容量是4
}

通过地址传递,初始化结构体里的内容。 


2.3栈空间的销毁

//销毁结构体里的内容,地址传递,才能改变实参的内容
void StackDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);//释放动态开辟的内存
	ps->a = NULL;

	ps->top = 0; //将结构体里的 相当于数组下标top 和 容量capacity 置为0
	ps->capacity = 0;
}

通过释放函数free释放动态开辟的内存,达到销毁栈空间的目的。 


2.4入栈的功能

//入栈,相当于往数组填充数据x
void StackPush(ST* ps,StackDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)//申请的动态空间满了,数组的元素和容量相等,需要增容
	{
		//用relloc函数申请动态空间,创建临时指针变量是怕动态开辟空间失败,增容一般是上一次的2倍
		//注意realloc和malloc函数里的参数,realloc有两个,malloc只有一个
		StackDataType* tmp = (StackDataType*)realloc(ps->a, sizeof(StackDataType) * ps->capacity * 2);

		if (tmp == NULL)//判断是否成功开辟动态空间
		{
			perror("the mistake is");
		}
		else  //增容成功
		{
			//printf("增容成功\n");
			ps->a = tmp;//将新开辟的动态空间地址赋给指针a
			ps->capacity *= 2;
		}
	}

	//不需要增容或增容之后
		ps->a[ps->top] = x;//想数组填充数据
		ps->top++;  //数组下标加1,方便后面下一次入栈;
						//同时top从0开始,填充一个数据top就自增一次,所以数据个数和top相等
}

需要注意的是,如果栈存放的数据,达到了栈空间动态开辟的容量,便需要增容的操作。 


2.5打印栈里数据的功能

//打印栈里的数据
void StackPrint(ST* ps)
{
	int i = 0;
	for (i = 0; i < ps->top; i++)//因为top初始化的时候是0,存一个数据,top就自增1一次
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

类似打印数组里的数据,遍历一次。 


2.6出栈的功能

//出栈,相当于数组最后一个数据除去,一般除数组最后一个元素的方法就是下标减1,效果上相当于除去了
void StackPop(ST* ps)
{
	assert(ps);

	assert(ps->top > 0);//防止数据里面没有数据,就不需要出栈
	                    //之前入栈的时候,因为top从0开始,填充一个数据top就自增一次,所以数据个数和top相等

	ps->top--;//一般除数组最后一个元素的方法就是下标减1,效果上相当于除去了
}

 解释请看注解,非常详细。


2.7返回栈顶的数据

//返回栈顶的元素,相当于数组的最后一个元素,元素的类型是StackDataType
StackDataType StackTop(ST* ps)
{
	assert(ps);

	assert(ps->top>0);//防止数据里面没有数据,就不用返回
					  //因为top从0开始,填充一个数据top就自增1一次,删除一个数据top就自减1一次,所以数据个数和top相等

	return ps->a[ps->top - 1];//因为下标top是初始化从0开始的,所以数组最后一个元素的下标是top-1
}

  解释请看注解,非常详细。


2.8返回栈里的数据的个数

//返回栈里的元素的个数,相当于数组里的元素的个数,就是我们前面说的top,个数的类型就是整型int
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;//栈里的元素个数和top相等
}

   解释请看注解,非常详细。


2.9判断栈里是否有数据

//判断栈里是否有元素,用布尔型bool,真就为1,假就为0
bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;//ps->top==0 为真,则返回值是1;为假,返回值是0
}

    解释请看注解,非常详细。


3、总代码

这是总代码的地址所在,需要的可以自取:放代码: 代码 - Gitee.com


4、 代码运行实例


二、线性表之队列

1、栈的表示和实现 

1.1队列的概念和结构

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

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

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

结构如下所示:  

 


 1.2队列的实现表示方法

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

而链表中,使用单链表的结构实现会更好。因为队列结构的入队列相当于单链表的尾插出队列相当于单链表的头删


2、设计队列的功能实现

2.1单链表节点的设计

typedef int QDataType;//类型重命名

typedef struct QueueNode//节点用结构体表示
{
	struct QueueNode* next;//指向下一个节点
	QDataType data;//队列中存储的数据
}QNode;

2.2队列的特殊处理

为了方便使用单链表的尾插(入队列),创建一个指针tail,指向链表的尾节点

为了方便使用单链表的头删(出队列),创建一个指针head,指向链表的第一个节点

结构体放入这两个变量,方便使用。

typedef struct Queue//定义一个结构体,用来存放链表的第一个节点和尾节点
					//第一个节点用于对头出(头删),尾节点用于队尾入(尾插)
{
	QNode* head;
	QNode* tail;
}Queue;

2.3初始化指针head和指针tail

//初始化第一个节点和尾节点
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;//初始化两个节点都用空指针NULL
}

我们会使用这两个指针,所以要先初始化指针head和tail,不然会变成野指针。 


2.4队尾入(尾插) 

// 队尾入(尾插)
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));//开辟新节点
	if (newnode == NULL)//防止动态空间开辟失败
	{
		printf("malloc fail\n");
		exit(-1);//结束函数
	}

	//开辟空间成功
	newnode->data = x;//将想放的数据放入节点里
	newnode->next = NULL;//单链表节点里的指针要先赋空指针NULL

	if (pq->tail == NULL)//判断链表里是否有节点
	{
		pq->head = pq->tail = newnode;//没有节点,将新节点赋给第一个节点head和尾节点tail
	}
	else
	{
		pq->tail->next = newnode;//有节点,直接单链表尾插
		pq->tail = newnode;//尾节点向后走一位,方便写一次队尾入(尾插)
	}
}

节点是通过动态内存开辟的,这样可以使避免内存空间的浪费,队尾入首先要判断链表里是否有节点若没有动态开辟的第一个节点的地址,要先赋给指针 head 和 tail,因为我们想让指针head指向第一个节点,指针tail指向尾节点。


2.5对头出(头删)

// 队头出
void QueuePop(Queue* pq)
{
	assert(pq);

	assert(pq->head);//防止链表里没有节点,就不需要队头出(头删)

	// 1、一个
	// 2、多个
	if (pq->head->next == NULL)//如果链表里只有一个节点的时候
	{
		free(pq->head);//直接free点这节点的空间
		pq->head = pq->tail = NULL;//将指针head和tail置空
	}
	else//链表有两个及以上的节点
	{
		QNode* next = pq->head->next;//先找到第一个节点head的下一个节点next
		free(pq->head);//释放第一个节点的空间
		pq->head = next;//将节点next作为新的第一个节点,方便下一次的队列出(头删)
	}
}

对头出(头删)的时候,我们要注意一种情况:如果链表只有 一个节点 或者 一直对头出(头删),直到最后一个节点,这个时候如果没有上面的 if 语句判断链表是否只有一个节点,那么当通过free(pq->head)最后一个节点的空间释放的时候,指针tail就会变成野指针,因为它所指向的那块空间被释放了,因为只有一个节点的时候,指针head和tail指向的都是这一个节点,所以要考虑这种情况。


2.6返回对头的数据(链表里的第一个节点的数据)

//访问对头的数据(链表里的第一个节点的数据)
QDataType QueueFront(Queue* pq)
{
	assert(pq);

	assert(pq->head);//防止链表没有节点,没有节点就访问不了结构体里的数据,不然就会报错

	return pq->head->data;//访问对头(第一个节点)里的数据
}

 解释请看注解,非常详细。


2.7返回队尾的数据(链表里的尾节点的数据)

//访问对尾的数据(链表里的尾节点的数据)
QDataType QueueBack(Queue* pq)
{
	assert(pq);

	assert(pq->head);//防止链表没有节点,没有节点就访问不了结构体里的数据,不然就会报错

	return pq->tail->data;//访问对尾(尾节点)里的数据
}

  解释请看注解,非常详细。


2.8返回队列(链表)里的数据个数

//队列(链表)里的元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur)//遍历一次,直到链表最后一个节点里的指针(NULL)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

   解释请看注解,非常详细。


2.9判断链表里是否有元素

//判断链表里是否有元素 
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;//看第一个节点是否为空指针,
							//为空就是没有元素,不为空就是有元素
}

    解释请看注解,非常详细。


2.10队列空间的销毁

//销毁空间,即返回每个节点开辟的空间
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)//遍历一次,直到链表最后一个节点里的指针(NULL)
	{
		QNode* next = cur->next;
		free(cur);//依次释放每个节点的空间
		cur = next;
	}

	pq->head = pq->tail = NULL;//释放完,指针head和 tail置空
}

因为节点是动态开辟的,所以通过函数free释放动态内存空间。 


3、总代码

这是总代码的地址所在,需要的可以自取:放代码: 代码 - Gitee.com


4、代码运行实例

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值