Stack基础知识1

来源:互联网 发布:迅雷看看 mac 编辑:程序博客网 时间:2024/05/29 15:46

栈(stack)在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。

栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。

在各种语言环境下,比如C++,JAVA或者C#中,对于栈都会提供两个基本的函数,即push和pop函数,分别用于向栈中压入对象和从栈中弹出对象。

栈的实现可以有两种方式,如下所示:

(1)链表实现。该方法使用链表存储数据,压栈的操作相当于在链表的开头插入一个新的节点,出栈的操作相当于在链表的开头删除开始的节点。

(2)数组实现。栈的另外一种实现方式就是使用数组,也是最常用的实现方式。我们使用一个数组对象来存储数据,并定义一个变量指示栈顶的位置。

我们知道,在JAVA中,数组对象时在初始化之后是不能改变大小的,但是对于栈来说,其大小应该能够根据实际情况自行变动的,这就需要我们在栈的实现过程中自己判断

数组是否已经装满,并自行扩充堆栈的大小。在JDK中,我们可以看到栈的实现是通过数组实现的,在Stack.java文件中,我们可以看到

class Stack<E> extends Vector<E>

在上面,我们看到Stack继承Vector,而Vector中的数据的实现是通过数组来存储的。

栈的应用有很多,下面讲述两个个经典的栈的应用。

(1)符号匹配

大家都知道,编译器在编译程序时都会为我们检查语法的错误。其中一项主要的内容就是检查符号是否是对应出现的。如[{}]就是一个符合语法的,而{[}]是不符合语法的。

使用栈实现对于文件的符号匹配的检测算法可以描述如下:

首先,建立一个空的栈,然后从文件中读取字符,如果符号是一个开符号,如“{”,“[”和“(”,则将该符号压入栈,如果是闭符号,此时如果栈是空的,则报告一个错误,

如果不是空的,则从栈中弹出一个开字符,如果弹出的开字符是与原闭字符相对应,则继续从文件中读取字符,如果不相对应,则报告一个错误。当读到文件的末尾时,检查栈

是不是空的,如果是空的,则没有语法错误,如果不是空的,则报告语法错误。

(2)后缀表达式

当我们计算一个数学计算表达式时,包含+,-,*,/,(,)和0-9十个数字,首先使用栈将数学计算表达式转换为后缀表达式,然后使用栈计算后缀表达式。

首先我们说一下如何将计算表达式转换为后缀表达式。在转换的过程中需要考虑到运算符的优先级,下面列出如下:

优先级运算符1括号()2负号-3乘方**4乘*,除/,求余%5加+,减-6小于<,小于等于<=,大于>,大于等于>=7等于==,不等于!=8逻辑与&&9逻辑或||

利用栈解析算数表达式的步骤如下:

(1)从左到右依次取得一个字符ch

(2)如果ch是一个字符,则直接输出该字符

(3)如果ch是一个运算符,则:

            a.如果ch=‘(',放入栈中;

            b.如果ch=‘)',依次输出栈中的运算符,直到遇到')';

            c.如果ch不是'('或者')',那么需要将ch和栈顶的字符top进行优先级的比较,如果ch的优先级比top的优先级高,那么将ch压入栈中,如果ch的优先级低于或者等于top的

优先级,那么输出top,并将ch压入栈中。

(4)如果表达式已经读取完成,而栈中还有运算符,则将运算符从栈中依次输出。

如果我们有表达式(A-B)*C+D-E/F,要翻译成后缀表达式,并且把后缀表达式存储在一个名叫output的字符串中,可以用下面的步骤。

(1)读取'(',压入堆栈,output为空
(2)读取A,是运算数,直接输出到output字符串,output = A
(3)读取'-',此时栈里面只有一个'(',因此将'-'压入栈,output = A
(4)读取B,是运算数,直接输出到output字符串,output = AB
(5)读取')',这时候依次输出栈里面的运算符'-',然后就是'(',直接弹出,output = AB-
(6)读取'*',是运算符,由于此时栈为空,因此直接压入栈,output = AB-
(7)读取C,是运算数,直接输出到output字符串,output = AB-C
(8)读取'+',是运算符,它的优先级比'*'低,那么弹出'*',压入'+",output = AB-C*
(9)读取D,是运算数,直接输出到output字符串,output = AB-C*D
(10)读取'-',是运算符,和'+'的优先级一样,因此弹出'+',然后压入'-',output = AB-C*D+
(11)读取E,是运算数,直接输出到output字符串,output = AB-C*D+E
(12)读取'/',是运算符,比'-'的优先级高,因此压入栈,output = AB-C*D+E
(13)读取F,是运算数,直接输出到output字符串,output = AB-C*D+EF
(14)原始字符串已经读取完毕,将栈里面剩余的运算符依次弹出,output = AB-C*D+EF/-

通过上面的算法之后我们可以得到一个运算表达式相对应的后缀表达式,接下来就是对后缀表达式进行解析,算得算数计算式的值。

解析过程如下所示:

(1)从左到右扫描表达式,一次取出一个数据ch;

(2)如果ch是操作数,则压入栈中;

(3)如果ch是操作符,就从栈中弹出操作符所需要的个数的操作数进行操作,并将运算结果压入栈中;

(4)如果数据处理完毕,则栈中最终剩下的数据就是最终运算的结果。

比如我们要处理一个后缀表达式1234+*+65/-,那么具体的步骤如下。

(1)首先1,2,3,4都是操作数,将它们都压入堆栈
(2)取得'+',为运算符,弹出数据3,4,得到结果7,然后将7压入堆栈
(3)取得'*',为运算符,弹出数据7,2,得到数据14,然后将14压入堆栈
(4)取得'+',为运算符,弹出数据14,1,得到结果15,然后将15压入堆栈
(5)6,5都是数据,都压入堆栈
(6)取得'/',为运算符,弹出数据6,5,得到结果1.2,然后将1.2压入堆栈
(7)取得'-',为运算符,弹出数据15,1.2,得到数据13.8,这就是最后的运算结果

实例代码:

public class PosfixParser
{
private static string expression = "1+4/(1+1)+2*(3+4)-6/3+5/(1/2+2/1)";
private static Stack myStack = new Stack();
private static StringBuilder posfixExpression = new StringBuilder();

public static void Main()
{
Console.WriteLine("This Midfix expression is: {0}", expression);
Console.WriteLine("The Posfix expression is: {0}", Parse());
Console.WriteLine("The result is {0}", Calculate());
Console.Read();
}

//将中缀表达式解析成后缀表达式
public static string Parse()
{
int i, j = 0;
char ch, ch1;
char[] A = expression.ToCharArray(); //将字符串转成字符数组,要注意的是,不能有大于10的数存在
char[] B = new char[A.Length]; //最后生成的后缀表达式会小于这个长度,因为有括号
int length = A.Length;

for(i=0; i<length; i++)
{
ch = A[i];

if( IsOperand( ch ) ) //如果是操作数,直接放入B中
{
B[j++] = ch;
}

else
{
if( ch == '(' ) //如果是'(',将它放入堆栈中
myStack.Push(ch);
else if( ch == ')') //如果是')'
{
while( !IsEmpty(myStack) ) //不停地弹出堆栈中的内容,直到遇到'('
{
ch = (char)myStack.Pop();
if( ch == '(' )
break;
else
B[j++] = ch; //将堆栈中弹出的内容放入B中
}
}
else //既不是'(',也不是')',是其它操作符,
比如+, -, *, /之类的
{
if( !IsEmpty( myStack ) )
{
do
{
ch1 = (char)myStack.Pop();//弹出栈顶元素
if(Priority(ch) > Priority(ch1)) //如果栈顶元素的优先级小于读取到的操作符
{
myStack.Push(ch1);//将栈顶元素放回堆栈
myStack.Push(ch);//将读取到的操作符放回堆栈
break;
}
else//如果栈顶元素的优先级比较高或者两者相等时
{
B[j++] = ch1; //将栈顶元素弹出,放入B中
if( IsEmpty(myStack) )
{
myStack.Push(ch); //将读取到的操作符压入堆栈中
break;
}
}
} while( !IsEmpty(myStack));
}
else //如果堆栈为空,就把操作符放入堆栈中
{
myStack.Push(ch);
}
}
}
}

while( !IsEmpty(myStack ) )
B[j++] = (char)myStack.Pop();//将堆栈中剩下的操作符输出到B中

for(i=0; i<B.Length; i++)
if( B[i] != '\0' ) //去除多余的空字符
posfixExpression.Append(B[i]);

return posfixExpression.ToString();
}
//计算后缀表达式的值
publicstatic double Calculate()
{
inti;
doubleno1, no2, ret;
charch;
char[]A = posfixExpression.ToString().ToCharArray();

myStack.Clear();

for(i=0;i<A.Length;i++)
{
ch
= A[i];
if(IsOperand(ch))//如果是操作数,直接 压入栈
{
myStack.Push((
double)(ch-48));
}
else//如果是操作符,就弹出两个数字来进行运算
{
no1
= (double)myStack.Pop();
no2
= (double)myStack.Pop();
ret
= GetValue(ch,no1, no2);
myStack.Push(ret);
//将结果压入栈
}
}

return(double)myStack.Pop();//弹出最后的运算结果
}

//对两个值利用运算符计算结果
privatestatic double GetValue(charop, double ch1, double ch2)
{
switch(op )
{
case'+':
returnch2 +ch1;
case'-':
returnch2 -ch1;
case'*':
returnch2 *ch1;
case'/':
returnch2 /ch1;
default:
return0;
}
}

//判断堆栈是否为空
privatestatic bool IsEmpty(Stack st)
{
returnst.Count ==0 ? true:false;
}

//判断是否是操作数
privatestatic bool IsOperand( charch )
{
char[]operators = { '+','-','*','/','(',')'};
for(inti=0;i<operators.Length;i++)
if(ch == operators[i] )
returnfalse;

returntrue;
}

//返回运算符的优先级
privatestatic int Priority( charch )
{
intpriority;

switch(ch )
{
case'+':
priority
= 1;
break;
case'-':
priority
= 1;
break;
case'*':
priority
= 2;
break;
case'/':
priority
= 2;
break;
default:
priority
= 0;
break;
}

returnpriority;
}
}