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、 为其他运算符,有以下步骤进行:
比较该运算符与临时栈栈顶指针的运算符的优先级,如果临时栈栈顶指针的优先级大于等于该运算符的优先级,弹出并添加到后缀表达式中,反复执行前面的比较工作,直到遇到一个栈顶指针的优先级低于该运算符的优先级,停止弹出添加并把该运算符压入栈中。
此时的比较过程如果出现栈顶的指针为‘(’,则停止循环并把该运算符压入栈中,注意:‘(’不要弹出来。
遍历完中缀表达式之后,检查临时栈,如果还有运算符,则全部弹出,并添加到后缀表达式中。