栈是一种特殊的线性表,它只能在一端进行插入或者删除操做,能进行操做的一端称为栈顶,另外一端则称为栈底。也因为这个特性,致使先进入的元素,只能后出,所以栈是后进先出的线性表。git
栈是一种线性表,所以它的存储能够是链式存储,也能够是顺序存储。链式存储的栈,称为链栈,顺序存储的栈,称为顺序栈。而下面实例中,使用go语言的可变数组slice完成栈的存储,所以是顺序存储。算法
栈的描述数组
type data interface {}
type Stack struct {
top int // 栈顶,表明栈顶的下标,-1为空栈
list []data // 从0开始存储
}
func New() *Stack{
s := &Stack{top:-1,list: []data{}}
return s
}
复制代码
在栈顶插入一个新元素,称为入栈,首先要判断栈是否已满,满就报错,不然就插入,并将栈顶上升一位。数据结构
// 因为使用的是可变数组,所以不会出现栈满的状况,因此如下代码不须要判断是否栈满,若是是不可变数组,或者是限制容量的栈,则要判断栈是否已满
func (s *Stack) Push (value data) {
s.list = append(s.list,value)
s.top++
}
复制代码
取出栈顶元素,并将栈顶降低一位,若是栈为空,则报错app
func (s *Stack) Pop() (data,error) {
if s.top == -1 {
return nil,errors.New("栈空")
}
data := s.list[s.top]
s.list = s.list[:s.top]
s.top--
return data,nil
}
复制代码
判断是否为空栈函数
func (s *Stack) IsEmpty() bool{
return s.top == -1
}
复制代码
获得栈顶元素的值,但不出栈post
func (s *Stack) GetTop() data{
if s.top == -1 {
return nil
}
return s.list[s.top]
}
}
复制代码
咱们平常生活中使用的算术表达式,例如:5+6/2-3*4,它由两类对象构成:ui
因为运算符号优先级不一样,并且运算符号位于运算数中,因此使得运算变得复杂了。 怎么理解呢?拿上面的表达式为例,当程序遍历到+号的时候,要作+运算,那么是5+6吗?很显然不是,由于6后面还有一个/运算符,并且/的优先级大于+,因此6是/的运算数,而不是+的运算数。那么反过来呢?当遍历到一个运算符号的时候,就已经知道对应的两个运算数,这样求值就变的简单了。伟大的科学家们,就此发明了后缀表达式,也称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,全部的计算按运算符出现的顺序,严格从左向右进行(再也不考虑运算符的优先规则)。spa
固然两个表达式,都是表达同一个意思。code
因为后缀表达式不须要考虑运算符的优先规则,所以求值算法就变得简单了:
一、从左到右依次遍历表达式;
二、遇到数字就直接入栈;
三、遇到操做符就弹出两个元素,先弹出的元素放到操做符的右边,后弹出的元素放到操做符的左边(左边的运算数先入栈,所以后出),将计算获得的结果再压入栈;
四、直到整个表达式遍历完,最后弹出的栈顶元素就是表达式最终的值。
以33-5+表达式为例,运行状态以下
func getPostfixExpressionResult(s string) int {
stack := Stack.New()
for _, value := range s {
if unicode.IsDigit(value) {
intValue, _ := strconv.Atoi(string(value))
stack.Push(intValue)
} else {
right, _ := stack.Pop()
left, _ := stack.Pop()
result := calculate(left.(int), right.(int), string(value)) // 封装的 + - * / 求值方法
stack.Push(result)
}
}
result, _ := stack.Pop()
return result.(int)
}
getPostfixExpressionResult("33-5+") // 5
复制代码
能够看得出算法时间复杂度为O(n),而且经过出栈入栈的形式就能够完成求值过程。
接下来看看中缀怎么转后缀,咱们先对比一下两个表达式:
能够看的出来,数字的相对位置是不变的,改变的是符号的位置,那么在转换的过程,咱们须要对比各类运算符号的优先级,而后将优先级高的运算符,先输出,低的后输出,这样在后缀表达式求值的时候,就能保存计算顺序不被改变。(左括号和右括号也当作运算符号)具体的算法步骤以下:
一、从左到右遍历中缀表达式;
二、若是是运算数,直接输出;
三、若是是运算符号:若优先级大于栈顶的运算符号(栈不为空),则将该运算符号压入栈中,由于若是该运算符号优先级比栈顶的大,说明要先被计算,那么它是后入的,所以在以后的操做中,必定比栈顶的符号先出,所以在后缀求值中,确定先被计算;
四、若是是运算符号:若优先级小于等于栈顶的运算符号(栈不为空),则将栈顶的运算符号弹出并输出,而后继续和下一个新的栈顶元素对比,直到优先级大于新的栈顶元素,就将该运算符号压入栈中;
五、左括号:括号里的表达式确定是要先计算的,所以当扫描到左括号的时候,直接将左括号压入栈中,而入了栈里的左括号,优先级就变到最低了。由于括号里的运算符要先运算;
六、右括号:将栈顶元素弹出并输入,直到遇到左括号(弹出,但不输出);
七、整个表达式遍历完以后,则将栈里的元素一一弹出并输出。
咱们以2*(9+6/3-2)+4为例:
// 利用hash的形式来判断运算符号的优先级
// 有兴趣能够看看百度百科里(后缀表达式)是怎么判断运算符号优先级的,颇有意思 (>▽<)
var opPriority = map[string]int{
"*":2,
"/":2,
"+":1,
"-":1,
"(":0,
}
func infixToPostfix(s string) string{
postfix := ""
stack := Stack.New()
for _,value :=range s{
if unicode.IsDigit(value) {
postfix += string(value)
} else {
op := string(value)
switch op{
case "+","-","*","/":
if stack.IsEmpty(){
stack.Push(op)
} else {
pop := stack.GetTop()
for opPriority[op] <= opPriority[pop.(string)] {
pop,_ = stack.Pop()
postfix += pop.(string)
if stack.IsEmpty() {
break
}
pop = stack.GetTop()
}
stack.Push(op)
}
case "(":
stack.Push(op)
case ")":
for !stack.IsEmpty() {
op,_ := stack.Pop()
if op.(string) == "(" {
break
}
postfix +=op.(string)
}
}
}
}
for !stack.IsEmpty(){
op,_ := stack.Pop()
postfix +=op.(string)
}
return postfix
}
infixToPostfix("2*(9+6/3-5)+4") // 2963/+5-*4+
复制代码
栈是一种被普遍应用的数据结构,除了刚刚举例的算术表达式求值以外,栈还用于函数调用及递归实现,回溯算法等等。在适当的时候选择栈,能够更加高效的解决问题。
ps:在上面的例子里,后缀表达式求值的程序,是一个不太正确的程序,准确的讲,它会把每一个数字当作运算数,即122*,它不会计算出24,而是变成了4,估计你们也猜到缘由了,由于程序是一个字符一个字符的遍历,而没有位数的划分,修正的话,能够用数组来存表达式,这样就能够正确的划分运算数。
Thanks!