今天把 栈的顺序存储结构 和 链式存储结构 聊个通透!

三遍定律:
对栈比较陌生的同学请点击这里
对栈比较陌生的同学请点击这里
对栈比较陌生的同学请点击这里

进入正题

1.栈的顺序存储结构及实现

(1) 栈的顺序存储结构

既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,对于栈这种只能一头插入删除的线性表,一般把数组下标为 0 的一端作为栈底,因为首元素都存在栈底,变化最小。

我们定义一个 top 变量来指示栈顶元素在数组中的位置,这 top 就如同中学物理学过的游标卡尺的游标,它可以来回移动,意味着栈顶的 top 可以变大变小,但无论如何游标不能超出尺的长度。同理,若存储栈的长度为 StackSize,则栈顶位置 top 必须小于 StackSize。当栈存在一个元素时,top 等于 0,因此通常把空栈的判定条件定为 top 等于 -1。

游标卡尺
栈的结构定义:

/* SElemType 类型根据实际情况而定,这里假设为 int */
typedef int SElemType;
typedef struct
{
	SElemType data[MAXSIZE];
	/* 用于栈顶指针 */
	int top;
} SqStack;

现在有一个栈,StackSize 是 5,则栈一般情况、空栈和满栈情况如图:

在这里插入图片描述

(2) 进栈操作

先上图:

在这里插入图片描述
对于进栈操作 push,其代码如下:

/* 插入元素 e 为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
	/* 栈满 */
	if (S->top = MAXSIZE - 1)
	{
		return ERROR;
	}
	/* 栈顶指针加一 */
	S->top++;
	/* 将新插入的元素赋值给栈顶空间 */
	S->data[S->top] = e;
	return OK;
}

(3) 出栈

出栈操作pop,代码如下:

/* 若栈不空,则删除 S 的栈顶元素,用 e 返回其值 */
Status Pop(SqStack *S, SElemType *e)
{
	if (S->top = -1)
	{
		return ERROR;
	}
	*e = S->data[S->top]
	/* 栈顶指针减一 */
	S->top--
	return OK;
}

进栈、出栈都没有涉及到任何循环语句,因此时间复杂度均是O(1)。

2.栈的链式存储结构及实现

(1) 栈的链式存储结构

栈的链式存储结构简称为链栈。

栈只是栈顶来做插入和删除操作,由于单链表有头指针,而栈顶指针也是必须的,那干吗不让它俩合二为一呢,所以比较好的办法是把栈顶放在单链表的头部,另外,都已经有了栈顶在头部了,单链表中比较常用的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。
如图:

在这里插入图片描述
对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间,如果真的发生,那此时的计算机操作系统已经面临死机崩溃的情况,而不是这个链栈是否溢出的问题。

但对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是 top = NULL 的时候。

链栈结构代码如下:

typedef struct StackNode
{
	SElemType data;
	struct StackNode *next;
} StackNode, *LinkStackPtr;
typedef struct LinkStack
{
	LinkStackPtr top;
	int count;
} LinkStack;

链栈的操作绝大部分都和单链表类似,只是在插入和删除上,特殊一些。

(2) 进栈

对于链栈的进栈push操作,假设元素值为e的新结点是s,top为栈顶指针。

进栈过程如图:

在这里插入图片描述
进栈代码如下:

/* 插入新元素 e 为新的栈顶元素 */
Status Push(LinkStack *S, SElemType e)
{
	LinkStackPtr s
		 = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data = e;
	/* 把当前的栈顶元素赋值给新结点的直接后继,如图中① */
	s->next = S->top;
	/* 将新的结点 s 赋值给栈顶指针,如图中② */
	S->top = s;
	S->count++;
	return OK;
}

(3)出栈

至于链栈的出栈pop操作,也是很简单的三句操作。假设变量 p 用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放 p 即可,如图:
在这里插入图片描述

链栈出栈代码:

/* 若栈不空,则删除S的栈顶元素,用e返回其值 */
Status Pop(LinkStack *S, SElemType *e)
{    
	LinkStackPtr p;    
	if (StackEmpty(*S))        
	return ERROR;    
	*e = S->top->data;    
	/* 将栈顶结点赋值给p,如图③ */    
	p = S->top;                   
	/* 使得栈顶指针下移一位,指向后一结点,如图④ */    
	S->top = S->top->next;        
	/* 释放结点p */    
	free(p);                      
	S->count--;    
	return OK;
}	

链栈的进栈push和出栈pop操作都很简单,没有任何循环操作,时间复杂度均为O(1)。

对比一下顺序栈与链栈,它们在时间复杂度上是一样的,均为O(1)。对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了一些内存开销,但对于栈的长度无限制。所以它们的区别和线性表中讨论的一样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值