1

栈与队列

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

允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。大话数据结构的程杰老师用“弹夹式手枪和左轮手枪”这个例子来比喻,很形象。

栈的插入操作,叫做进栈,也称压栈入栈
栈的删除操作,叫做出栈,也称弹栈

栈的抽象数据类型

对于栈来讲,理论上线性表的操作特性它都具备,可由于它的特殊性,所以针对它的操作上会有变化。特别是插入和删除操作,我们改名为push和pop。这对于Java的面向对象来说可能体会比较深,如ArrayList和Vector都是继承抽象类AbstractList,而Vector又是Stack的父类,所以Stack是具有普通线性表的功能,但又具有自己push和pop的方法。。

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

逆波兰表达式(Reverse Polish Notation, RPN)又称后缀表达式。是由波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为后缀表示。

与后缀表达式对应的是中缀表达式,也就是我们常用的9+(3-1)x3+10/2这种表达式,中缀表达式是二元运算符置于与之相关的两个运算对象之间。

后缀表达式的计算规则:
规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到符号就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终结果。反正运算符是不会进栈的。
举个栗子,如9 3 1 - 3 * + 10 2 / +步骤如下:

  1. 初始化一个空栈,此栈用来对要运算的数字(操作数)进出使用;
  2. 后缀表达式的前三个都是数字,所以9、3 、1进栈;
  3. 接下来是减号-,所以将栈中的操作数1和操作数3出栈,操作数1作为减数,操作数3作为被减数。则就是3-1=2,然后再将2进栈;
  4. 接着是数字3进栈;
  5. 后面是乘号,也就是把操作数3和操作数2出栈,23=6,然后将6进栈;
  6. 接下来是加号+,所以把操作数6和9出栈,9+6=15,将15进栈;
  7. 接着是10与2两数字进栈;
  8. 后面是除号/,因此,栈顶的2与10出栈,10/2=5,将5进栈;
  9. 最后一个符号是加号+,所以将15和5出栈并相加,15+5=20,将20进栈;
  10. 遍历完毕,20出栈,栈变为空,20就是最终结果。

中缀表达式转后缀表达式(计算器实现原理):

规则是:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即为后缀表达式的一部分;如果是符号,则判断此符号与栈顶符号的优先级,如果栈顶元素是右括号或栈顶元素的优先级高于或等于栈顶符号(乘除优先加减),则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式位置。反正数字(操作数)和括号是不会进栈的。
举个栗子,将中缀表达式9+(3-1)x3+10/2转为后缀表达式9 3 1 - 3 * + 10 2 / + 步骤如下:

  1. 初始化一个空栈,用于对符号(操作符)进出栈使用;
  2. 第一个字符是数字9,输出9,后面是符号加号+,加号+进栈;
  3. 第三个字符是左括号“(”,依然是符号,因其只是左括号,还未匹配,故进栈;
  4. 第四个字符是数字3,输出3,总表达式为 9 3,接着是减号-,进栈;
  5. 接下来是数字1,输出1,总表达式为 9 3 1,后面符号是右括号“)”,此时,我们需要去匹配之前的左括号“(”,所以栈顶依次出栈,并输出,直到左括号“(”出栈为止。此时左括号上方只有减号-,因此输出减号-。此时表达式为 9 3 1 -;
  6. 接着是字符是乘号×,由于乘号优先级高于栈顶元素(此时是加号+),此时将乘号×入栈。
  7. 接着字符是数字3,输出3,此时表达式为9 3 1 - 3;
  8. 跟着是字符是加号+,由于加号+优先级低于栈顶元素(此时是乘号×),所以栈中元素出栈并输出直到没有比当前字符优先级更低的符号,也就是输出乘号×和加号+,然后当前元素加号+则进栈,此时表达式为 9 3 1 - 3 * +;
  9. 接着字符是数字10,输出10,此时表达式为 9 3 1 - 3 * + 10;
  10. 接着是字符除号/,由于栈顶元素现在是加号,优先级低于除号/,所以除号/入栈;
  11. 接着是数字2,输出数字2,此时表达式为 9 3 1 - 3 * + 10 2;
  12. 遍历完毕,将栈内元素出栈,分别是除号/和加号+,此时表达式为 9 3 1 - 3 * + 10 2 / +为最终结果。

总结下:
将中缀表达式转化为后缀表达式(数字(操作数)和括号是不会进栈的);
将后缀表达式进行运算得出结果(运算符是不会进栈的);

队列

队列(queue)是只允许在一端进行插入操作,而在另外一端进行删除操作的线性表
再次提起下,以加强记忆,既然时线性表,就说明可以用数组或链表的物理结构进行存储。

队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为堆尾,允许删除的一端称为队头。

队列的抽象数据类型

同样是线性表,队列也有类似线性表的各种操作,不同的就是插入数据只能在队尾进行,删除数据只能在队头进行。

ADT 队列(Queue)
Data
    同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。

Operation
    InitQueue(*Q);        //初始化操作,建立一个空队列Q
    DestroyQueue(*Q);     //若队列Q存在,则销毁它
    ClearQueue(*Q);        //将队列Q清空
    QueueEmpty(Q);        //若队列Q为空,返回true,否则返回false    
    GetHead(Q,*e);        //若队列Q存在且非空,用e返回队列Q的队头元素
    EnQueue(*Q,e);        //若队列Q存在,插入新元素e到队列Q中并成为队尾元素
    Dequeue(*Q,*e);        //删除队列Q中队头元素,并用e返回其值
    QueueLength(Q);        //返回队列Q的元素个数

循环队列:把队列的头尾相接的顺序存储结构

总结

使用了栈和队列结合起来,可以写出一个简单的计算器,我用C写了一个,感兴趣可以点击下

参考:《大话数据结构》


电脑杂技集团
208 声望32 粉丝

这家伙好像很懂计算机~