棧是一種特殊的線性表,它只能在一端進行插入或者刪除操做,能進行操做的一端稱爲棧頂,另外一端則稱爲棧底。也因爲這個特性,致使先進入的元素,只能後出,所以棧是後進先出的線性表。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!