您的位置:首页 > 理论基础 > 数据结构算法

数据结构——栈和队列

2016-10-09 16:30 169 查看
首先,栈和队列都是表。也就是说我们可以通过数组或者链表来实现栈和队列。

 

队列

队列就如同现实中的排队,大家按顺序排好,第一个人“出队”了,第二个人就成为第一个人,新来的人就要在最后一个位置“入队”。

对应的,队列结构的两个操作就是Dequeue和Enqueue。

 

队列的实现很简单,使用单链表即可,Dequeue操作即删除第一个元素,Enqueue操作只需要在最后一个元素后插入一个元素即可。

 

优先队列是一种更高级的队列,它根据各个元素的“优先级”来决定排队的顺序,优先队列也称为堆,关于堆的讨论在其他部分。

 

栈和队列都是表,但队列是“先入先出”,即先入队的元素将更先出队,而栈则是“后入先出”,即后入栈的元素将先出栈。

可以将栈看做一个“瓶子”,入栈的元素即“球”,先进入瓶子的球将在瓶子的底下,而后进入的则在上面,因为瓶子只有上面一个口,所以先进去的球得等后进去的球拿出来了才能拿得出来。

很显然,实现栈是很容易的,假设使用链表,则我们只需要将入栈元素插在第一个位置,出栈也是删除第一个元素即可。

 

 

关于栈的应用

栈的应用有很多,比较常“听说”的就是函数的调用,当当前函数调用了另一个函数的时候,我们将当前函数的有关信息入栈,等调用函数结束了,我们就让栈弹出一个元素,很显然,这样的顺序符合我们调用函数的习惯,假设a执行一半调用b,b执行一半调用c,显然c结束后应该弹出b继续执行,b结束后又弹出a继续执行。

很显然,递归调用将用去不少的栈空间(系统负责)!同时也令我们发现,所有正确的递归都可以被彻底除去!(通过循环、自己设立的栈)只是有一些递归的消除会很麻烦,这也是我们愿意使用递归的原因:简单明了易理解。当然,当你觉得递归的开销无法接受时,可以自己去消除递归!

 

不过我们这儿打算先谈论一个有意思的栈的应用,那就是“后缀表达式”,这个东西可以帮助我们制作一个“计算器”!

 

首先,我们简介一下栈在平衡符号方面的应用(这对于制作计算器也是有用的)

一般来说,有(就应该有),有 [
就应该有 ]
,形如 [
()]
我们说符号是平衡的,而([
)]我们则说符号不平衡,这很好理解,但对于计算机该如何实现这样的“判断”呢?

假设我们只会输入这两种括号(只是为了免去异常处理,只关心核心部分),那么判断输入结束时是否符号平衡只需要用一个(字符)栈即可。

当我们输入一个括号后,我们先检查是否为(和[
,如果是我们让其入栈,如果不是(和 ] ,  而是)或
] ,我们从栈中弹出一个符号(如果此时栈为空则显然可以报错了),如果弹出的符号与当前输入的符号匹配(()或[]),则不做操作,如果不匹配则报错,当所有输入结束时,如果栈不为空,也报错。

很显然这样利用栈来判断平衡符号是没有问题的!(自制计算器时,这种判断符号平衡的方法也将被用上!)

 

 

后缀表达式

后缀表达式即普通计算表达式的一种表示方式!也称为逆波兰记法

我们平时所见到的

1+2*3-4

就是中缀表达式!(显然,“缀”的意思是运算符所处的位置~)

 

首先,我们平时所用的+-*/都是二元运算符,也就是它们都需要两个运算对象来参与运算!对于人来说,我们可以直接找到*/这两个优先级更高的运算符,然后计算对应的式子,例如我们直接找到*,然后看到2*3,计算出6后替换掉2*3将表达式变成1+6-4,

从这开始,我们接下来可以执行依次从左到右的计算了:

首先拿到1,然后拿到+,这时候还差一个“参数”,接着拿到6,于是+的参数凑齐了,计算1+6得出结果放回去得到7-4,然后拿到7拿到-,在拿到4凑齐-的参数,计算7-4得出最终结果3

 

 

但是对于计算机来说,最大的问题在于如何实现“优先级高的先计算”?

正是为了解决这个问题,我们有了后缀表达式!

 

 

如果将上述中缀表达式转换为后缀表达式,则是:

123*+4-

我们先不管后缀表达式是如何得到的,我们先来分析计算机如何计算这样的后缀表达式。

计算机当然是使用栈来实现后缀表达式的计算了,不然我们为什么要在这时候讨论它呢?

利用栈来计算后缀表达式是这样的:

当接收一个数时,压栈,当接收一个运算符时,弹出栈顶元素作为右侧运算对象,再弹出栈顶元素作为左侧运算对象(即弹出两个元素,但先弹出的作为右侧对象),运算后将结果压栈,继续。模拟运算过程是这样的:

1入栈,2入栈,3入栈,得到*取出3和2,计算2*3得到6,6入栈,得到+取出6和1,计算1+6得到7,7入栈,4入栈,得到-取出4和7,计算7-4得到3,3入栈,此时栈内只有一个元素,表达式也已经结束,所以运算完毕,结果为3.

 

更复杂的后缀表达式的计算也是一样的!所以现在的问题就只剩下如何得到后缀表达式了(因为我们只会输入中缀表达式)

幸运的是,得到后缀表达式依然是利用栈来完成的~

首先假设我们得到的中缀表达式为

a+b*c+(d*e+f)*g

正确的对应后缀表达式为

abc*+de*f+g*+

接下来我们说转换的过程:当读到一个数时,直接输出到后缀表达式中,当读到一个运算符时,我们就不能直接输出到后缀表达式了(不然不就是原样输出了?),而是应该将其压入一个栈中!(但并不是简单的入栈,这里还伴随着优先级和出栈的判断!)

首先,读到(时,直接入栈

然后,读到)时,将栈顶元素逐个弹出并输出到后缀表达式中,直至遇到(,并将(弹出丢弃,当然读到的)也是丢弃的

接着,对于+-*/四个运算符,当我们读到任意一个时,我们判断栈顶元素的优先级是否低于读到的运算符,如果高于或等于,则弹出栈顶元素(一直弹,直到遇到一个优先级低于所读取到的运算符或者(为止!),然后压入读取到的运算符

例如读到*,栈内从顶到底依次是*/+,则我们弹出*和/,然后将读取到的*压入栈!栈内成为*+

 

模拟该过程,转换a+b*c+(d*e+f)*g:

a输出              栈:             表达式:a

+入栈              栈:+         表达式:a

b输出              栈:+          表达式:ab

*入栈              栈:*+         表达式:ab

c输出              栈:*+        表达式:abc

+,弹*+,入栈      栈:+          表达式:abc*+

(入栈               栈:(+         表达式:abc*+

d输出              栈:(+         表达式:abc*+d

*入栈               栈:*(+        表达式:abc*+d

e输出              栈:*(+         表达式:abc*+de

+,弹*,入栈       栈:+(+        表达式:abc*+de*

f输出               栈:+(+         表达式:abc*+de*f

),弹+(,丢()                   栈:+
         表达式:abc*+de*f+

*,入栈             栈:*+         表达式:abc*+de*f+

g,输出             栈:*+         表达式:abc*+de*f+g

读取结束,全部弹出   栈空         表达式:abc*+de*f+g*+

 

至此,结束!

 

讲完这里,相信自己在控制台应用程序制作一个“计算器”已经可以开始动手了!关键点在于:判断输入是否有误,将字符分门别类为数和运算符,转换为后缀表达式,计算后缀表达式!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: