栈
目录
栈
栈的定义
- 栈是一个先入后出的有序列表
- 栈是限制线性表中的元素的插入和删除,只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端为变化的一端,称为栈顶,另一端称为栈底。
运用场景
- 子程序的调用,在调往子程序前,会先将下一个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用
- 表达式的转换(中缀表达式转后缀表达式)与求值。
- 二叉树的遍历
- 图的深度优先(depth-first)搜索法。
代码实现栈
- 通过数组来模拟
- 定义栈顶变量top,初始化为-1
- 入栈:
- top++
- element[top] = data
- 出栈:
- object = element[top]
- element[top] = null
- top–
package stack
import "fmt"
type ArrayStack struct {
// 切片
Element []interface{}
// 栈顶指针
Top int64
// 栈大小
MaxSize int64
}
// NewArrayStack 构造函数
func NewArrayStack(size int64) *ArrayStack {
return &ArrayStack{
Element: make([]interface{}, size),
Top: -1,
MaxSize: size,
}
}
func (s *ArrayStack) IsFull() bool {
if s.Top == s.MaxSize-1 {
return true
} else {
return false
}
}
func (s *ArrayStack) IsEmpty() bool {
if s.Top == -1 {
return true
} else {
return false
}
}
func (s *ArrayStack) Pop() interface{} {
if s.IsEmpty() {
fmt.Println("栈中没有元素")
return nil
} else {
ele := s.Element[s.Top]
s.Element[s.Top] = nil
s.Top--
return ele
}
}
func (s *ArrayStack) Push(ele interface{}) {
if s.IsFull() {
fmt.Println("栈已满。。。")
return
} else {
s.Top++
s.Element[s.Top] = ele
}
}
func (s *ArrayStack) List() {
if s.IsEmpty() {
fmt.Println("栈为空")
}
for {
if s.Top == -1 {
break
}
pop := s.Pop()
fmt.Println("元素:", pop)
}
}
// Peek 查看栈顶元素
func (s *ArrayStack) Peek() interface{} {
if s.IsEmpty() {
fmt.Println("栈为空")
return nil
}
ele := s.Element[s.Top]
return ele
}
栈的运用
使用栈完成表达式的计算
- 通过一个index值来遍历表达式
- 如果是数字,就入数栈
- 如果是符号分情况
- 如果当前栈为空,直接入符号栈
- 如果符号栈有数据,需要比较,如果当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数,从符号栈中pop出一个符号,进行运算将得到的结果放入数栈中,将当前的操作符放入符号栈中,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
- 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
- 最后在数栈中只有一个数时,表示运算结束。
中缀表达式转后缀表达式
- 初始化两个栈:运算符栈s1和存储中间结果的栈s2;
- 从左到右扫描中缀表达式
- 遇到操作数时将其压入s2中
- 遇到运算符时比较其与s1栈顶运算符的优先级
- 如果s1为空,或栈顶运算符为左括号,则直接将次运算符压入s1中
- 如果运算符优先级比栈顶运算符的高,也将其压入s1中
- 如果运算符优先级比栈顶运算符的低,将s1的栈顶运算符弹出并压入s2中,再次进行(4.1)的操作与s1中新的栈顶元素比较;
- 遇到括号时:
- 如果是左括号,直接压入s1中
- 如果是右括号,则依次弹出s1栈顶的运算符,并压入s2中,直到遇见左括号为止,此时将这一对括号丢弃
- 重复2-5的步骤直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素,将结果逆序后即为后缀表达式
代码实现
package main
import (
"fmt"
"my-practice/5.stack/stack"
"regexp"
)
func main() {
fmt.Println("请输入表达式:")
// 接收表达式
var expression string
fmt.Scanln(&expression)
// 遍历切片中的字符
expressionList := toInfixExpressionList(expression)
fmt.Println("中缀表达式:", expressionList)
suffixExpression := parseSuffixExpression(expressionList)
fmt.Println("后缀表达式:", suffixExpression)
}
// 中缀表达式转 string list : eg 12.34-(4+8)*5
func toInfixExpressionList(s string) []string {
// 定义一个 切片
var charSlice = make([]string, len(s))
// expression := "1+2-5+5*6"
var numStr string
index := 0
runes := []rune(s)
for i := 0; i < len(runes); {
if runes[i] < 48 || runes[i] > 57 {
charSlice[index] = string(runes[i])
index++
i++
} else {
numStr = ""
// 拼接数字
for i < len(runes) && ((runes[i] >= 48 && runes[i] <= 57) || 46 == runes[i]) {
numStr += string(runes[i])
i++
}
charSlice[index] = numStr
index++
}
}
return charSlice
}
// 中缀表达式转后缀表达式
func parseSuffixExpression(strSlice []string) []string {
// 定义一个符号栈
var operatorStack = stack.NewArrayStack(64) //创建栈
// 定义一个中间数组
var middleList = make([]string, 4)
// 遍历 strSlice 切片
for _, char := range strSlice {
// 如果是一个数,就加入 middleList,通过正则表达式来匹配
reg, _ := regexp.Compile("\\d+")
if reg.Match([]byte(char)) {
middleList = append(middleList, char)
} else if "(" == char { // 如果是左括号直接入栈
operatorStack.Push(char)
} else if ")" == char { // 如果是右括号,则依次弹出s1栈顶的运算符,并压入s2中,直到遇见左括号为止,此时将这一对括号丢弃
for operatorStack.Peek() != "(" {
middleList = append(middleList, fmt.Sprintf("%s", operatorStack.Pop()))
}
operatorStack.Pop() // 消除小括号
} else { // 考虑运算符的优先级问题
// 当char的优先级小于等于栈顶的运算符的优先级时
for !operatorStack.IsEmpty() && operationLevel(char) <= operationLevel(fmt.Sprintf("%s", operatorStack.Peek())) {
// 判断优先级
middleList = append(middleList, fmt.Sprintf("%s", operatorStack.Pop()))
}
operatorStack.Push(char)
}
}
// 将运算符栈中剩余的元素加入 middleList 中
for !operatorStack.IsEmpty() {
middleList = append(middleList, fmt.Sprintf("%s", operatorStack.Pop()))
}
return middleList
}
// 判断运算符的优先级
func operationLevel(char string) int {
switch char {
case "+":
return 1
case "-":
return 1
case "*":
return 2
case "/":
return 2
default:
return 0
}
}
实际运用-逆波兰计算器
在上面的方法中已经计算出了逆波兰表达式(逆序输出后缀表达式) 通过遍历切片计算即可
func calculate(slice []string) interface{} {
// 创建一个栈
stack := stack.NewArrayStack(64)
// 循环遍历后缀表达式
for _, str := range slice {
if "" == str {
continue
}
// 使用正则表达式来匹配数字
reg, _ := regexp.Compile("\\d+")
// 如果是数字先入栈
if reg.Match([]byte(str)) {
stack.Push(str)
} else {
// 否则弹出两个数,并运算再入栈
num1, _ := strconv.ParseFloat(fmt.Sprintf("%s", stack.Pop()), 64)
num2, _ := strconv.ParseFloat(fmt.Sprintf("%s", stack.Pop()), 64)
var res float64 = 0.0
switch str {
case "+":
res = num1 + num2
case "-":
res = num2 - num1
case "*":
res = num2 * num1
case "/":
res = num2 / num1
default:
fmt.Println("异常的计算符")
continue
}
// 将结果压入栈中,有个坑,这个地方的值是float类型
stack.Push(fmt.Sprintf("%f", res))
}
}
return stack.Pop()
}
在main中添加代码:
fmt.Println("计算结果:", calculate(suffixExpression))
测试结果
输入:12.34-(4+8)*5
中缀表达式: [12.34 - ( 4 + 8 ) * 5 ]
后缀表达式: [12.34 4 8 + 5 * - ]
计算结果: -47.660000