一部分有工做經驗的老司機對數據結構是很熟悉了,而一部分剛參加工做或者剛入行的人對數據結構是略懂一二甚至是感到陌生,但願本篇文章可讓老司機更熟悉數據結構的實現,不懂數據結構的小白對數據結構的實現有必定的瞭解。javascript
本系列文章使用多種語言實現經常使用的數據結構,包括目前使用最多的 Java
,21 世紀興起的 Go
,前端領域的 JavaScript
,目的是儘量的讓更多的人可以看的懂、看的明白。總體上採用文字、圖和代碼的方式進行介紹,代碼默認使用 Go
語言版本,其它語言請參考完整的源代碼,對應的源代碼見下文。前端
全部已經更新的內容在 Github 上,若是對你有幫助請給個 star,謝謝 \ (•◡•) /,你的確定是我繼續更新的動力 ☺。java
各類語言實現代碼:Go Java JavaScriptgit
默認使用 Go 語言實現。github
棧和隊列同樣也是一種特殊的線性表。它只能在表尾進行插入和刪除操做。在進行插入和刪除操做的一端被稱爲棧頂,另外一端稱爲棧底。向一個棧放入新元素稱爲進棧、入棧或壓棧,從一個棧取出元素稱爲出棧或退棧。每個新元素都會放在以前放入的元素之上,刪除時會刪除最新的元素,因此棧有先進後出(FILO—first in last out)的特色。golang
使用數組來實現棧。express
定義結構體和建立結構體的函數:數組
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
,計算該表達式的值。
定義兩個棧,一個爲數棧,一個爲符號棧。
截取字符串(須要考慮多位數),判斷是否爲數字,若是爲數字,將字符串壓入數棧,若是爲運算符壓入符號棧。
壓入符號棧前判斷符號棧是否爲空。
若是符號棧爲空,直接入棧。
若是符號棧不爲空,從符號棧中試探出一個符號,判斷優先級。
若是當前將要入棧的符號優先級小於或等於從符號棧中取出來的優先級
從數棧中彈出兩個數,再從符號棧中彈出一個符號,進行運算,將運算
結果壓入數棧,再將要入棧的符號入棧。不然將當前符號直接入棧。
不斷的從操做符棧中取出一個符號,從數棧中取出兩個數,進行計算,將計算結果壓入數棧,
當符號棧爲空時跳出循環,此時數棧中的最後一個元素就是最終的計算結果。
在以前實現的棧結構體中增長兩個方法:
// 判斷棧是否爲空 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-
,計算出該後綴表達式的值。
思路分析:
步驟:
代碼實現
定義操做符,計算兩個數,判斷是否爲數值等方法:
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
將中綴表達式轉換成後綴表達式,步驟以下:
遇到運算符時,比較其與 stack1 棧頂運算符的優先級
代碼實現
定義一個將字符串表達式轉換成切片的函數:
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
注意:這裏只能計算整數,且表達式先後不能有空格。