数据结构——栈和队列
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*+
至此,结束!
讲完这里,相信自己在控制台应用程序制作一个“计算器”已经可以开始动手了!关键点在于:判断输入是否有误,将字符分门别类为数和运算符,转换为后缀表达式,计算后缀表达式!
队列
队列就如同现实中的排队,大家按顺序排好,第一个人“出队”了,第二个人就成为第一个人,新来的人就要在最后一个位置“入队”。对应的,队列结构的两个操作就是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*+
至此,结束!
讲完这里,相信自己在控制台应用程序制作一个“计算器”已经可以开始动手了!关键点在于:判断输入是否有误,将字符分门别类为数和运算符,转换为后缀表达式,计算后缀表达式!
相关文章推荐
- 数据结构——基本数据结构之队列
- 数据结构之顺序队列
- 【数据结构与算法基础】以数组实现的循环队列 / Circular Queue implemented by array
- 基础数据结构(栈,队列)
- [翻译]C#数据结构与算法 – 第五章栈与队列(Part 2)
- 数据结构之队列的实现(c语言)
- 数据结构:队列的实现
- 关于linux内核中 等待队列 数据结构之思考
- 关于linux内核中 等待队列 数据结构之思考
- 数据结构(三)——队列及实现、循环队列实现
- 数据结构:用队列模拟理发店的排队情况(C#)
- [翻译]C#数据结构与算法 – 第五章栈与队列(Part 1)
- 面向数据c++数据结构之基本数据结构(队列)--【美】Jan Harrington 陈博译
- 数据队列结构与操作
- [翻译]C#数据结构与算法 – 第五章栈与队列(Part 2)
- 数据结构之循环队列
- C#数据结构之队列
- 数据结构实现(队列类模板)
- 数据结构之表(7)队列的顺序实现
- 数据结构(五)——双链表、链式栈、链式队列 及实现