栈与队列Part1--栈的定义与应用、顺序栈、两栈共享空间、链栈的部分操作具体实现(C语言描述)

此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢!


一、栈(Stack)的定义:

  • 栈(Stack): 是限定仅在表尾进行插入和删除操作的线性表。是一种特殊的线性表。

栈又称为 后进先出(Last In First Out) 的线性表,简称LIFO结构。
(Examples:Photoshop and Word’s “Undo” function,browsers’s “back” function。)

允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。

栈的插入操作叫做进栈(Push),也称压栈、入栈。
栈的删除操作叫做出栈(Pop),也称弹栈。
在这里插入图片描述


二、栈的抽象数据类型:

对于栈而言,线性表的操作特性它基本都具备,但操作上会有些区别。特别是插入与删除操作。

Data: 性质同线性表。数据元素具有相同的类型,相邻元素具有前驱和后驱的关系。

[ADT of stacks:]

Operations:
	InitStack(*S): Set up an empty stack. //初始化操作,建立一个空栈S。 
	DestroyStack(*S): Destroy the stack when it exists. //若栈存在,则销毁它。 
	ClearStack(*S): Empty the stack. //将栈清空。
	StackEmpty(S): (Bool Type)Judge whether the stack is empty. //若栈为空,则返回true,否则返回false。
	GetTop(S, *e): If the stack exists and not empty, give the top element value to "e" and return "e". //若栈存在且非空,用e返回S的栈顶元素。
	Push(*S, e): If the stack exists, push new element "e" into stack to be the top. //若栈存在,插入新元素e到栈S中并成为栈顶元素。 
	Pop(*S, *e): Delete the top element in stack, give its value to "e", and return "e". //删除栈S中的栈顶元素,并用e返回其值。
	StackLength(S): Return the amount of elements in the stack. //返回栈中元素个数。
	
end ADT 

三、栈的顺序存储结构(Sequential Stack)定义(顺序栈):

首先,为了示意清楚以及方便,先声明定义:

#define OK 1 
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int SElemType; //SElemType的类型根据实际需求而定,此处假设为int。 
typedef int Status;/*"Status" is defined as a type of a function, with its returned value representing the result of the function.
					(Status是函数的类型,它的返回值势函数结果状态代码,0或1.)*/ 

由于我们只能从表尾进行插入与删除的操作,我们选择下标为0的一端作为栈底,因为它的变化最小,更适合作为栈底。

此时,我们需要定义一个新的变量 “top” 来指示栈顶元素在栈中的位置(top 应该时刻保持小于栈的长度StackSize)。同时,我们规定当栈为空栈时,top = -1;因此当栈中只有一个元素时,top = 0.
在这里插入图片描述
接下来是顺序栈SqStack的结构代码:

/*Since we can only insert and take out element from one side, 
  it's better to choose the start of the array as the bottom of the stack because it changes less. */

/*Here we definite a variable "top" to instruct the top element's position in the stack. "top" should be smaller than "StackSize".
  We usually consider "top" as "-1" when the stack is empty, thus, "top" is "0" when there's only one element. */

[The structure code of a SqStack:] //顺序栈的结构代码
typedef struct
{
	SElemType data[MAXSIZE]; //SElemType为typedef的int类型数据。
	int top; //As the top pointer.
}SqStack;

四、顺序栈部分操作具体实现方法:

  • "Push"(进栈): T(n)=O(1)
[Achieve the operation of "push" in a SqStack:] //顺序栈的进栈操作 
/*Insert a new element "e" as the new top element. */
Status Push ( SqStack *S, SElemType e )
{
	if( S->top==MAXSIZE-1 ) //The stack is full.
	{
		return ERROR;
	}
	S->data[++S->top] = e;
	
	return OK;
}
T(n) = O(1)
  • "Pop"(出栈): T(n)=O(1)
[Achieve the operation of "pop" in a SqStack:] //顺序栈的出栈操作 
/*If the stack isn't empty, delete the top element and return its value using "e". */
Status Pop ( SqStack *S, SElemType *e )
{
	if( S->top==-1 )
	{
		return ERROR;
	}
	*e = S->data[S->top--];
	
	return OK;
} 
T(n) = O(1)

在这里插入图片描述


五、两栈共享空间定义(SqDoubleStack):

虽然顺序栈不存在SqList那样插入删除元素需要大量元素的问题,但它有一个很大的缺陷:必须实现确定数组存储空间大小。 由此引出:两栈共享空间–用一个数组来存储两个栈。

  • 方法:数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即 index=0处。而另一个栈的栈底为数组的末端,即 index=n-1处。这样的话,两栈若增加元素,就是两端点向中间延伸。

  • 关键思路:两栈顶指针top1 top2是在数组的两端,向中间靠拢。
    (“top1 + 1 == top2” 代表栈满。)

 [The structure code of two stacks sharing space.] //两栈共享空间的结构代码
typedef struct
{
	SElemType data[MAXSIZE];
	int top1; //Stack1's top pointer.
	int top2; //Stack2's top pointer.
}SqDoubleStack;
// "top1+1==top2" represents that the stack is full. 

在这里插入图片描述


六、共享栈部分操作具体实现方法:

对于SqDoubleStack的进出栈方法,我们需要的参数除了插入元素值 e 之外,还需要一个判断是对栈1还是栈2进行操作的栈号参数stackNumber

  • "Push"(进栈): T(n)=O(1)
/*As for the operation of "Push" and "Pop"  of SqDoubleStack, 
  we need another parameter "stackNumber" to judge the stack we are to operate is 1 or 2.*/
[Achieve the operation of "push" in a SqDoubleStack.]
Status Push ( SqDoubleStack *S, SElemType e, int stackNumber )
{
	if( S->top1+1 ==S->top2 ) //Representing the stack is full.
	{
		return ERROR;
	}
	if( stackNumber==1 )
	{
		S->data[++S->top1] = e;
	}
	if( stackNumber==2 )
	{
		S->data[--S->top2] = e;
	}
	
	return OK;
}
T(n) = O(1)
  • "Pop"(出栈): T(n)=O(1)
[Achieve the operation of "pop" in a SqDoubleStack.] 
Status Pop ( SqDoubleStack *S, SElemType *e, int stackNumber )
{
	if( stackNumber==1 )
	{
		if( S->top1==-1 )
		{
			return ERROR;
		}
		*e = S->data[S->top--];
	}
	if( stackNumber==2 )
	{
		if( S->top2==MAXSIZE )
		{
			return ERROR:
		}
		*e = S->data[S->top2++];
	}
	
	return OK;
}
T(n) = O(1)

对于SqDoubleStack这样的数据结构,通常都是当两栈的空间需求有相反关系时,即“此消彼长”时才使用。这样使用,该数据结构才比较有意义,否则两栈都在增长,很快就会因栈满而溢出了。

同时应该注意,这种方法是针对两个具有相同数据类型的栈的技巧。如果是不同数据类型的栈,则不能使用,会使问题更加复杂。


七、栈的链式存储结构(Linked Stack)定义(链栈):

  • 栈的链式存储结构,简称链栈。

由于栈只是栈顶来进行插入和删除操作,因此我们选择把栈顶放在单链表的头部,让栈顶指针与原有的头指针合二为一。另外通常对于链栈来说,是不需要头结点的。对于空栈来说,链表原定义是头指针指向NULL,因此链栈的空其实就是 top=NULL。 因此链栈的操作大部分都和单链表类似,但是在插入和删除操作上会特殊一些。

不同于顺序栈,对于链栈而言,基本不存在栈满的情况,除非内存已没有可供使用的空间。因此如果真的发生栈满的情况,那此时计算机操作系统已经面临死机崩溃的情况,而不是这个链栈是否溢出的问题了。

在这里插入图片描述

接下来是链栈的结构代码:

#define OK 1 
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int SElemType; //SElemType的类型根据实际需求而定,此处假设为int。 
typedef int Status;/*"Status" is defined as a type of a function, with its returned value representing the result of the function.
					(Status是函数的类型,它的返回值势函数结果状态代码,0或1.)*/ 
					
[The structure code of a linked stack.] //链栈的结构代码
typedef struct StackNode
{
	SElemType data; //SElemType的类型根据实际需求而定,此处假设为int。  
	struct StackNode *next;
}StackNode, *LinkStackPtr;

typedef struct LinkStack
{
	LinkStackPtr top;
	int count;
}LinkStack;

八、链栈部分操作具体实现方法:

  • "Push"(进栈):T(n) = O(1);

在这里插入图片描述

[Achieve the operation of "Push" in a linked stack.] //链栈的进栈操作。 
/* Insert a new element "e" as the new top element.*/ 
Status Push ( LinkStack *S, SElemType e )
{
	LinkStackPtr newnode = (LinkStackPtr)malloc( sizeof(StackNode) ); //Create a new node for the new element.
	newnode->data = e;
	newnode->next = S->top; //Let the current top element become the new node's next element.
	S->top = newnode; //Let the top pointer points to the new node.
	S->count++;
	
	return OK;
}
T(n) = O(1);	
  • "Pop"(出栈):T(n) = O(1);
    在这里插入图片描述
[Achieve the operation of "Pop" in a linked stack.] //链栈的出栈操作。 
/*If the stack is not empty, delete the top element of the stack and return its value by "e".*/
Status Pop ( LinkStack *S, SELemType *e )
{
	LinkStackPtr p;
	if( StackEmpty(*S) )
	{
		return ERROR;
	}
	*e = S->top->data;
	p = S->top;
	S->top = S->top->next;
	free(p);
	S->count--;
	
	return OK;
}
T(n) = O(1);

九、顺序栈与链栈的对比:

  • 时间性能:顺序栈与链栈相同,都是T(n)=O(1)。
  • 空间性能:顺序栈的优势是存取时定位很方便,但它要求需要事先确定一个固定的长度,因此可能会存在内存空间的浪费或者不足的问题;链栈的优势是栈的长度基本不受限制,但它要求每个元素都有指针域,因此会增加内存开销。

结论:若在栈的使用过程中,元素变化不可预料,有时很大有时很小,那么最好使用链栈。反之,如果元素变化在可控范围内,建议使用顺序栈会更好一些。


十、栈的应用:

①.递归:
通常来说,编译器都是使用栈这种数据结构来实现递归操作的,这是因为递归需要存储某些数据,并且后面又以存储的逆序恢复这些数据,以提供之后使用,这与栈的特点十分贴合。
(递归函数:一个直接调用自己或通过一系列的调用语句间接地调用自己的函数。)

简单来说就是:在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。而在退回阶段,位于栈顶的局部变量、参数值以及返回地址被弹出栈,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。

②.四则运算表达式求值 – 后缀(逆波兰)表示法 :

  • 后缀(逆波兰)(Reverse Polish Notation, RPN)表示法:一种不需要括号的后缀表达法。

(以下以算式 "9+(3-1)x3+10/2"为例)
后缀表达式:所有的符号都是在要运算数字的后面出现。并且此时没有括号出现。比如,该例子用后缀表达式表示为:“931-3*+102/+”。

*后缀表达式计算方法
从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到符号就将处于栈顶的数字出栈,进行运算,运算结果进栈,一直到最终获得结果。若两数字配上减号/除号,则先出栈的数字为减数/除数。 如:此例子中的3和1,1为减数。

*中缀表达式转化为后缀表达式方法
从左到右遍历中缀表达式中的每个数字和符号,遇到是数字就输出,即成为后缀表达式的一部分。遇到符号,则判断其与栈顶符号的优先级。是右括号或者优先级不高于栈顶符号,则栈顶元素依次出栈并输出,并将当前符号进栈。直到最终输出完整后缀表达式为止。此处注意先后顺序,当前判断的符号无需输出,等待栈内符号输出之后再进栈。(中缀表达式即为我们平时所用的标准四则运算表达式)

可以发现这整个过程都充分利用了栈LIFO的特性来处理。
在这里插入图片描述

总的来说,要让计算机具有处理我们通常的标准四则运算表达式的能力,最重要的就是两步:

  1. 将中缀表达式转化为后缀表达式(栈用来进出运算的符号);
  2. 将后缀表达式进行运算得到结果(栈用来运算进出的数字)。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值