栈的定义

  • 栈是一个先入后出的有序列表
  • 栈是限制线性表中的元素的插入和删除,只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端为变化的一端,称为栈顶,另一端称为栈底。

运用场景

  • 子程序的调用,在调往子程序前,会先将下一个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  • 处理递归调用
  • 表达式的转换(中缀表达式转后缀表达式)与求值。
  • 二叉树的遍历
  • 图的深度优先(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
}

栈的运用

使用栈完成表达式的计算

  1. 通过一个index值来遍历表达式
  2. 如果是数字,就入数栈
  3. 如果是符号分情况
    1. 如果当前栈为空,直接入符号栈
    2. 如果符号栈有数据,需要比较,如果当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数,从符号栈中pop出一个符号,进行运算将得到的结果放入数栈中,将当前的操作符放入符号栈中,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
  4. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
  5. 最后在数栈中只有一个数时,表示运算结束。

中缀表达式转后缀表达式

  1. 初始化两个栈:运算符栈s1和存储中间结果的栈s2;
  2. 从左到右扫描中缀表达式
  3. 遇到操作数时将其压入s2中
  4. 遇到运算符时比较其与s1栈顶运算符的优先级
    1. 如果s1为空,或栈顶运算符为左括号,则直接将次运算符压入s1中
    2. 如果运算符优先级比栈顶运算符的高,也将其压入s1中
    3. 如果运算符优先级比栈顶运算符的低,将s1的栈顶运算符弹出并压入s2中,再次进行(4.1)的操作与s1中新的栈顶元素比较;
  5. 遇到括号时:
    1. 如果是左括号,直接压入s1中
    2. 如果是右括号,则依次弹出s1栈顶的运算符,并压入s2中,直到遇见左括号为止,此时将这一对括号丢弃
  6. 重复2-5的步骤直到表达式的最右边
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出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
0%