栈 & 队列(1)

1.栈的理解


栈(stack)时限定仅在尾表进行插入和删除操作的线性表。

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

栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表



2.栈的插入与删除


栈的特殊之处就是在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。这就使得:栈底是固定的,最先进栈的只能在栈底。

栈的插入操作,叫做进栈,也称压栈、入栈。

栈的删除操作,叫做出栈,也有的叫作弹栈。


举例来说,现有3个整型数字元素1、2、3依次进栈,会有哪些出栈次序?

· 第一种:1、2、3进,再3、2、1出。这是最简单的最好理解的一种,出栈次序为321

· 第二种:1进,1出,2进,2出,3进,3出。也就是进一个出一个,出栈次序为123

· 第三种:1进,2进,2出,1出,3进,3出。出栈次序为213

· 第四种:1进,1出,2进,3进,3出,2出。出栈次序为132

· 第五种:1进,2进,2出,3进,3出,1出。出栈次序为231



3.栈的抽象数据类型


ADT 栈(stack)

DATA

  同线性表。元素具有相同的类型,相邻元素具有前驱后继关系。

Operation

InitStack(* S):初始化操作,建立一个空栈S

DestroyStack(* S):若栈存在,则销毁它

ClearStack(* S):将栈清空

StackEmpty( S ) :若栈为空,返回true,否则返回false

GetTop(S, * e):若栈存在且非空,用e返回S的栈顶元素

Push(* S,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素

Pop(* S, * e):删除栈S中栈顶元素,并用e返回其值

StackLength(S):返回栈S的元素个数

endADT



4.进栈与出栈操作


栈的结构定义

typedef int SElemType;

typedef struct

{

SElemType data[MAXSIZE];

int top;(用于栈顶指针)

}SqStack;


进栈操作push

Status Push (SqStack *S, SElemType e)

{

if( s -> top == MAXSIZE - 1 )  //栈满

{

return ERROR;

}

S -> top++;  // 栈顶指针加一

S -> data[S -> top] = e;  // 将新插入元素赋给栈顶空间

return OK;

}


出栈操作Pop

Status Pop(SqStack *S, SElemType *e)

{

if(S -> top == -1 )

return ERROR;

* e = S -> data[S -> top];

S -> top --;

return OK;

}



5.链栈的结构代码


栈的链式存储结构,简称为链栈,对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间,如果真的发生,那此时的计算机操作系统已经面临死机崩溃的情况,而不是这个链栈是否溢出的问题

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


typedef struct StackNode

{

SElemType data;

struct StackNode *next;

}StackNode, * LinkStackPtr;


typedef struct LinkStack

{

LinkStackPtr top;

int count;

}LinkStack;



6.栈的链式存储结构 - 进栈及出栈


Push:

Status Push (LinkStack *S, SElemType e)

{

LinkStackPtr s = (LinkStackPtr) malloc(sizeof(StackNode));

s -> data = e;

s -> next = S -> top;

S -> top = s;

S -> count++;

return OK;

}


Pop:

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;

}



7.栈的作用


栈的引用简化了程序设计问题,划分了不同关注层次,使思考范围缩小,更加聚焦于我们要解决的问题核心。反之,像数组等,因为要分散精力区考虑数组的下标增减等细节问题,反而掩盖了问题的本质。


所以现在的许多高级语言,比如JAVA,C#等都有对栈结构的封装,你可以不用关心它的实现细节,就可以直接使用Stack的push和pop方法,非常方便。



8.递归定义


一个直接调用自己或通过一系列的调用语句间接的调用自己的函数,称作递归函数。每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。、


递归能使程序结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。但大量的递归调用会建立函数副本,会耗费大量的时间和内存。迭代则不需要反复调用函数和占用额外的内存。



9.后缀表达式

运用后缀表达式进行计算的具体做法:

建立一个栈S 。从左到右读表达式,如果读到操作数就将它压入栈S中,如果读到n元运算符(即需要参数个数为n的运算符)则取出由栈顶向下的n项按操作数运算,再将运算的结果代替原栈顶的n项,压入栈S中 。如果后缀表达式未读完,则重复上面过程,最后输出栈顶的数值则为结束。



10.栈的应用----四则运算表达式求值(转)

计算机实现转换:

将中缀表达式转换为后缀表达式的算法思想:

·开始扫描;

·数字时,加入后缀表达式;

·运算符:

a. 若为 '(',入栈;

b. 若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ;

c. 若为 除括号外的其他运算符, 当其优先级高于除'('以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。

·当扫描的中缀表达式结束时,栈中的的所有运算符出栈

人工实现转换

这里我给出一个中缀表达式:a+b*c-(d+e)

第一步:按照运算符的优先级对所有的运算单位加括号:式子变成了:((a+(b*c))-(d+e))

第二步:转换前缀与后缀表达式

前缀:把运算符号移动到对应的括号前面

则变成了:-( +(a *(bc)) +(de))

把括号去掉:-+a*bc+de 前缀式子出现

后缀:把运算符号移动到对应的括号后面

则变成了:((a(bc)* )+ (de)+ )-

把括号去掉:abc*+de+- 后缀式子出现

发现没有,前缀式,后缀式是不需要用括号来进行优先级的确定的。如表达式:3+(2-5)*6/3

后缀表达式 栈

3_________________+

3 ________________+(

3 2 _______________+(-

3 2 5 -_____________ +

3 2 5 - _____________+*

3 2 5 - 6 * ___________+/

3 2 5 - 6 *3 __________+/

3 2 5 - 6 *3 /+________

("_____"用于隔开后缀表达式与栈)

另外一个人认为正确的转换方法:

遍历中缀表达式的每个节点,如果:

1、 该节点为操作数

直接拷贝进入后缀表达式

2、 该节点是运算符,分以下几种情况:

A、 为“(”运算符

压入临时堆栈

B、 为“)”运算符:

不断地弹出临时堆栈顶部运算符直到顶部的运算符是“(”为止,从栈中删除'('。并把弹出的运算符都添加到后缀表达式中。

C、 为其他运算符,有以下步骤进行:

比较该运算符与临时栈栈顶指针的运算符的优先级,如果临时栈栈顶指针的优先级大于等于该运算符的优先级,弹出并添加到后缀表达式中,反复执行前面的比较工作,直到遇到一个栈顶指针的优先级低于该运算符的优先级,停止弹出添加并把该运算符压入栈中。

此时的比较过程如果出现栈顶的指针为‘(’,则停止循环并把该运算符压入栈中,注意:‘(’不要弹出来。

遍历完中缀表达式之后,检查临时栈,如果还有运算符,则全部弹出,并添加到后缀表达式中。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值