多種語言實現數據結構之棧

前言

一部分有工做經驗的老司機對數據結構是很熟悉了,而一部分剛參加工做或者剛入行的人對數據結構是略懂一二甚至是感到陌生,但願本篇文章可讓老司機更熟悉數據結構的實現,不懂數據結構的小白對數據結構的實現有必定的瞭解。javascript

本系列文章使用多種語言實現經常使用的數據結構,包括目前使用最多的 Java,21 世紀興起的 Go,前端領域的 JavaScript,目的是儘量的讓更多的人可以看的懂、看的明白。總體上採用文字、圖和代碼的方式進行介紹,代碼默認使用 Go 語言版本,其它語言請參考完整的源代碼,對應的源代碼見下文。前端

全部已經更新的內容在 Github 上,若是對你有幫助請給個 star,謝謝 \ (•◡•) /,你的確定是我繼續更新的動力 ☺。java

棧(stack)

各類語言實現代碼:Go Java JavaScriptgit

默認使用 Go 語言實現。github

簡介

棧和隊列同樣也是一種特殊的線性表。它只能在表尾進行插入和刪除操做。在進行插入和刪除操做的一端被稱爲棧頂,另外一端稱爲棧底。向一個棧放入新元素稱爲進棧、入棧或壓棧,從一個棧取出元素稱爲出棧或退棧。每個新元素都會放在以前放入的元素之上,刪除時會刪除最新的元素,因此棧有先進後出(FILO—first in last out)的特色。golang

多種語言實現數據結構之棧

實現

使用數組來實現棧。express

  • 定義數組用來存儲棧元素
  • 定義棧中元素最大大小 maxSize
  • 定義棧頂,初始值爲 -1
  • 入棧方法 push
  • 出棧方法 pop

定義結構體和建立結構體的函數:數組

type Stack struct {
    array []string // 存放棧元素的切片(數組沒法使用變量來定義長度)
    maxSize int // 最大棧元素大小
    top int // 棧頂
}

func NewStack(size int) *Stack {
    return &Stack {
        array:   make([]string, size),
        maxSize: size,
        top:     -1, // 初始化爲 -1
    }
}

入棧方法:數據結構

func (s *Stack) Push(elem string) error {
    // 判斷棧是否已滿
    if s.top == s.maxSize - 1 {
        return errors.New("stack is full")
    }
    s.top++ // 棧頂加 1
    s.array[s.top] = elem
    return nil
}

出棧方法:app

func (s *Stack) Pop() (string, error) {
    if s.top == -1 {
        return "", errors.New("stack is empty")
    }
    elem := s.array[s.top]
    s.top-- // 棧頂減 1
    return elem, nil
}

爲了方便查看輸出結果,從新定義 String 方法:

// 從新定義 String 方法,方便輸出
func (s *Stack) String() string {
    str := "["
    for i := s.top; i >= 0; i-- {
        str += s.array[i] + " "
    }
    str += "]"
    return str
}

測試代碼:

func main() {
    // 建立一個棧
    stack := array.NewStack(3)
    // 入棧
    _ = stack.Push("one")
    _ = stack.Push("two")
    _ = stack.Push("three")

    // 棧滿,沒法入棧
    err := stack.Push("four")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(stack)

    elem1, _ := stack.Pop()
    elem2, _ := stack.Pop()
    elem3, _ := stack.Pop()

    fmt.Println("出棧:", elem1)
    fmt.Println("出棧:", elem2)
    fmt.Println("出棧:", elem3)

    // 棧空沒法出棧
    _, err = stack.Pop()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(stack)
}

運行輸出:

stack is full
[three two one ]
出棧: three
出棧: two
出棧: one
stack is empty
[]

綜合計算器

使用棧實現一個加減乘除的計算器。假設一個字符串爲 3+5*3-6,計算該表達式的值。

思路分析

  1. 定義兩個棧,一個爲數棧,一個爲符號棧。

  2. 截取字符串(須要考慮多位數),判斷是否爲數字,若是爲數字,將字符串壓入數棧,若是爲運算符壓入符號棧。

  3. 壓入符號棧前判斷符號棧是否爲空。

    • 若是符號棧爲空,直接入棧。

    • 若是符號棧不爲空,從符號棧中試探出一個符號,判斷優先級。

    • 若是當前將要入棧的符號優先級小於或等於從符號棧中取出來的優先級

      從數棧中彈出兩個數,再從符號棧中彈出一個符號,進行運算,將運算

      結果壓入數棧,再將要入棧的符號入棧。不然將當前符號直接入棧。

  4. 不斷的從操做符棧中取出一個符號,從數棧中取出兩個數,進行計算,將計算結果壓入數棧,

    當符號棧爲空時跳出循環,此時數棧中的最後一個元素就是最終的計算結果。

畫圖分析

多種語言實現數據結構之棧

多種語言實現數據結構之棧

多種語言實現數據結構之棧

多種語言實現數據結構之棧

代碼實現

在以前實現的棧結構體中增長兩個方法:

// 判斷棧是否爲空
func (s *Stack) IsEmpty() bool {
    return s.top == -1
}

// 窺視棧頂元素
func (s *Stack) Peek() string {
    if s.IsEmpty() {
        return ""
    }
    return s.array[s.top]
}

定義操做符結構體,定義相關操做符對應的優先級和計算方法:

type Operation struct {
    operation string
    priority  int
    optFunc   func(num1, num2 int) int
}

// 定義相關操做符對應的優先級和計算方法
var operations = map[string]Operation{
    "+": {"+", 1, func(num1, num2 int) int {
        return num1 + num2
    }},
    "-": {"-", 1, func(num1, num2 int) int {
        return num1 - num2
    }},
    "*": {"*", 2, func(num1, num2 int) int {
        return num1 * num2
    }},
    "/": {"/", 2, func(num1, num2 int) int {
        return num1 / num2
    }},
}

定義計算器結構體和相關方法:

type Calculator struct {
    numStack       *array.Stack // 數棧
    operationStack *array.Stack  // 符號棧
}

func NewCalculator() *Calculator {
    numStack := array.NewStack(10)
    operationStack := array.NewStack(10)
    return &Calculator{numStack: numStack, operationStack: operationStack}
}

// 判斷是不是操做符號
func (cal *Calculator) isOperation(opt string) bool {
    _, ok := operations[opt]
    return ok
}

// 判斷是否爲數字
func (cal *Calculator) isNum(char string) bool {
    matched, _ := regexp.MatchString("\\d+", char)
    return matched
}

// 計算操做符的優先級
func (cal *Calculator) priority(opt1, opt2 string) int {
    operation1, ok1 := operations[opt1]
    operation2, ok2 := operations[opt2]
    if ok1 && ok2 {
        return operation1.priority - operation2.priority
    } else {
        panic(fmt.Sprintf("請檢查運算符: %s, %s\n", opt1, opt2))
    }
}

// 計算結果
func (cal *Calculator) calculateNum(num1, num2 int, opt string) int {
    optFunc := operations[opt].optFunc
    if opt == "-" || opt == "/" {
        // 由於出棧後兩數的位置顛倒,需交換兩個數的位置
        num1, num2 = num2, num1
    }
    return optFunc(num1, num2)
}

func (cal *Calculator) calculateNumFromStack() int {
    // 從數棧中彈出兩個數,從符號棧中彈出一個符號
    numStr1, _ := cal.numStack.Pop()
    numStr2, _ := cal.numStack.Pop()
    opt, _ := cal.operationStack.Pop()
    num1, _ := strconv.Atoi(numStr1)
    num2, _ := strconv.Atoi(numStr2)
    // 計算值
    return cal.calculateNum(num1, num2, opt)
}

計算表達式的核心方法以下:

func (cal *Calculator) Calculate(expression string) {
    if expression == "" {
        return
    }
    var index int
    var number string
    for index < len(expression) {
        char := expression[index : index+1]
        // 判斷是否爲符號
        if cal.isOperation(char) {
            // 判斷符號棧是否爲空
            if cal.operationStack.IsEmpty() {
                // 壓入符號棧
                _ = cal.operationStack.Push(char)
            } else {
                // 符號棧不爲空,判斷優先級
                opt := cal.operationStack.Peek()
                // char 優先級小於等於 elem
                if cal.priority(char, opt) <= 0 {
                    // 計算值
                    result := cal.calculateNumFromStack()
                    // 將計算結果入數棧
                    _ = cal.numStack.Push(strconv.Itoa(result))
                }
                // 將當前操做符入符號棧
                _ = cal.operationStack.Push(char)
            }
        } else if cal.isNum(char) {
            // 向後面再取一位判斷是否爲數字
            if index < len(expression)-1 && cal.isNum(expression[index+1:index+2]) {
                number += char
                index++
                continue
            }
            // 壓入數棧
            _ = cal.numStack.Push(number + char)
            number = ""
        } else {
            panic("沒法識別的字符:" + char)
        }

        index++
    }

    // 所有數和符號都壓入對應的棧後,取出計算
    // 符號棧爲空,跳出循環
    for !cal.operationStack.IsEmpty() {
        // 計算值
        result := cal.calculateNumFromStack()
        // 將計算結果入數棧
        _ = cal.numStack.Push(strconv.Itoa(result))
    }
    // 彈出最終結果
    result, _ := cal.numStack.Pop()
    fmt.Printf("表達式執行結果: %s=%s\n", expression, result)
}

測試代碼:

func main() {
    calculator := NewCalculator()

    calculator.Calculate("3+5*3-6")
    calculator.Calculate("30+5*3-6")
    calculator.Calculate("130+5*3-6")
}

輸出:

表達式執行結果: 3+5*3-6=12
表達式執行結果: 30+5*3-6=39
表達式執行結果: 130+5*3-6=139

逆波蘭計算器

簡介

逆波蘭計算器使用逆波蘭表達式來計算表達式的值。逆波蘭表達式也叫後綴表達式,後綴表達式指的是運算符寫在操做數以後,好比 12+,它是計算機比較容易計算的一種表達式,由於計算機採用棧結構,執行先進後出的順序。與之對應的有前綴表達式,中綴表達式,咱們人通常識比較容易理解的是中綴表達式,好比 3+5*3-6 就屬於中綴表達式。

後綴表達式計算

假設一個後綴表達式爲 353*+2-,計算出該後綴表達式的值。

思路分析:

  1. 循環讀取每一個字符,判斷是不是數字。
  2. 若是是數字直接入棧。
  3. 若是是運算符,從棧中彈出兩個數,計算表達式的值,將結果壓入棧中。

步驟:

  1. 將 3,5,3 壓入棧中。
  2. 讀取到 * 時,從棧中彈出兩個數,棧頂彈出一個數 3,次棧頂彈出一個數 5。
  3. 計算 3 * 5,結果等於 15,將結果壓入棧中。
  4. 讀取到 + 時,從棧中彈出 15 和 3。
  5. 計算 15 + 3,結果等於 18,將 18 壓入棧中。
  6. 將 2 壓入棧中。
  7. 讀取到 - ,從棧中彈出 2 和 18。後一個數減去前一個數即 18 - 2。
  8. 計算 18 - 2,結果等於 16,將結果壓入棧中。
  9. 循環結束後,棧中的 16 就是表達式的值。

代碼實現

定義操做符,計算兩個數,判斷是否爲數值等方法:

type Opt struct {
    operation string
    priority  int
    optFunc   func(num1, num2 int) int
}

// 定義相關操做符對應的優先級和計算方法
var opts = map[string]Opt{
    "+": {"+", 1, func(num1, num2 int) int {
        return num1 + num2
    }},
    "-": {"-", 1, func(num1, num2 int) int {
        return num1 - num2
    }},
    "*": {"*", 2, func(num1, num2 int) int {
        return num1 * num2
    }},
    "/": {"/", 2, func(num1, num2 int) int {
        return num1 / num2
    }},
}

// 計算結果
func calculateNum(num1, num2 int, opt string) int {
    optFunc := opts[opt].optFunc
    if opt == "-" || opt == "/" {
        // 由於出棧後兩數的位置顛倒,需交換兩個數的位置
        num1, num2 = num2, num1
    }
    return optFunc(num1, num2)
}

func isNum(s string) bool {
    matched, _ := regexp.MatchString("\\d+", s)
    return matched
}

計算後綴表達式的方法:

func calSuffixExpression(expr []string) int {
    stack := array.NewStack(len(expr))
    for _, str := range expr {
        if isNum(str) {
            _ = stack.Push(str)
            continue
        }
        _, ok := opts[str]
        if !ok {
            panic("無效的運算符:" + str)
        }
        // 計算
        numStr1, _ := stack.Pop()
        numStr2, _ := stack.Pop()
        num1, _ := strconv.Atoi(numStr1)
        num2, _ := strconv.Atoi(numStr2)
        result := calculateNum(num1, num2, str)
        // 入棧
        _ = stack.Push(strconv.Itoa(result))
    }
    elem, _ := stack.Pop()
    result, _ := strconv.Atoi(elem)
    return result
}

測試代碼以下:

func main() {
    expr := "3 5 3 * + 2 -"
    // 假設數和數或符號之間有空格
    expressions := strings.Split(expr, " ")
    result := calSuffixExpression(expressions)
    fmt.Printf("後綴表達式 %s 的計算結果爲:%d\n", expr, result)
}

輸出:

後綴表達式 3 5 3 * + 2 - 的計算結果爲:16

中綴轉後綴表達式

將中綴表達式轉換成後綴表達式,步驟以下:

  1. 初始化兩個棧,一個運算符棧 stack1 和另外一個儲存中間結果的棧 stack2。
  2. 從左至右掃描中綴表達式。
  3. 遇到數字時,將其壓入 stack2。
  4. 若是是 "(" 號直接壓入 stack1。
  5. 若是是 ")" 號,依次彈出 stack1 中棧頂的元素,並壓入 stack2 中,直到遇到 "(" 將這一對括號丟棄。
  6. 遇到運算符時,比較其與 stack1 棧頂運算符的優先級

    • 若是 stack1 爲空或棧頂運算符爲左括號 "(",則直接將此運算符入棧。
    • 若是優先級比棧頂運算符低或者相等,將 stack1 棧頂的運算符彈出並壓入到 stack2 中,再次轉到 6-1 步,與 stack1 中新的棧頂運算符相比較,最後將當前運算符壓入 stack1。
  7. 重複 2 - 6,直到表達式末尾。
  8. 將 stack1 中剩餘的運算符依次彈出並壓入 stack2 中。
  9. 依次彈出 stack2 中的元素,將結果逆序就是轉換後的後綴表達式。

代碼實現

定義一個將字符串表達式轉換成切片的函數:

func exprToSlice(expr string) []string {
    var expressions []string
    for i := 0; i < len(expr); i++ {
        char := expr[i : i+1]
        if isNum(char) {
            // 向後面繼續判斷是否爲數字
            for i + 1 < len(expr) && isNum(expr[i+1:i+2]) {
                char += expr[i+1:i+2]
                i++
            }
        }
        expressions = append(expressions, char)
    }
    return expressions
}

判斷兩個操做符的優先級函數:

func priority(opt1, opt2 string) int {
    operation1, ok1 := opts[opt1]
    operation2, ok2 := opts[opt2]
    if ok1 && ok2 {
        return operation1.priority - operation2.priority
    } else {
        panic(fmt.Sprintf("請檢查運算符: %s, %s\n", opt1, opt2))
    }
}

接下來就是關鍵的中綴表達式轉後綴表達式函數:

func infixToSuffix(infix []string) []string {
    // 初始化兩個棧,一個運算符棧 stack1 和另外一個儲存中間結果的棧 stack2
    stack := array.NewStack(len(infix))
    var suffixes []string // 因爲中間結果棧不須要彈出元素,可使用數組來保存
    // 循環表達式
    for _, str := range infix {
        // 遇到數字時,將其放入 suffixes
        if isNum(str) {
            suffixes = append(suffixes, str)
            continue
        }
        if str == "(" {
            // 若是是 ( 直接入棧
            _ = stack.Push(str)
            continue
        }
        if str == ")" {
            for stack.Peek() != "(" {
                // 彈出 stack 中棧頂的元素,並追加到 suffixes
                elem, _ := stack.Pop()
                suffixes = append(suffixes, elem)
            }
            // 彈出 (,消除一對 ( )
            _, _ = stack.Pop()
            continue
        }
        // 若是是運算符
        opt, ok := opts[str]
        if ok {
            if stack.IsEmpty() || stack.Peek() == "(" {
                // 若是 stack 爲空或棧頂運算符爲左括號 "(",則直接將此運算符入棧
                _ = stack.Push(str)
                continue
            }
            // 棧不爲空,而且當前字符串的優先級小於等於棧頂的元素
            for !stack.IsEmpty() && priority(opt.operation, stack.Peek()) <= 0 {
                elem, _ := stack.Pop()
                // 將棧頂的元素追加到 suffixes
                suffixes = append(suffixes, elem)
            }
            // 直接入棧
            _ = stack.Push(str)
        } else {
            panic("沒法識別的字符:" + str)
        }
    }
    for !stack.IsEmpty() {
        // 將 stack 中剩餘的運算符依次追加到 suffixes
        elem, _ := stack.Pop()
        suffixes = append(suffixes, elem)
    }
    // 由於這裏用的是數組,它裏面元素的順序就是棧元素出棧後逆序排列的順序
    return suffixes
}

測試代碼以下:

func main() {
    expr := "1+((2+3)*4)-5"
    expressions := exprToSlice(expr)
    fmt.Printf("將中綴表達式放入切片, 結果爲:%v\n", expressions)

    expressions = infixToSuffix(expressions)
    fmt.Printf("中綴表達式轉換成後綴表達式, 結果爲:%v\n", expressions)

    result := calSuffixExpression(expressions)
    fmt.Printf("計算表達式%s, 結果爲:%v\n", expr, result)
}

輸出:

將中綴表達式放入切片, 結果爲:[1 + ( ( 2 + 3 ) * 4 ) - 5]
中綴表達式轉換成後綴表達式, 結果爲:[1 2 3 + 4 * + 5 -]
計算表達式1+((2+3)*4)-5, 結果爲:16

注意:這裏只能計算整數,且表達式先後不能有空格。

相關文章
相關標籤/搜索