咱們平常生活中,都須要將物品排列,或者安排事情的前後順序。更通俗地講,咱們買東西時,人太多的狀況下,咱們要排隊,排隊也有前後順序,有些人早了點來,排完隊就離開了,有些人晚一點,纔剛剛進去人羣排隊。算法
數據是有順序的,從數據1
到數據2
,再到數據3
,和平常生活同樣,咱們須要放數據,也須要排列數據。segmentfault
在計算機的世界裏,會常常聽見兩種結構,棧(stack)
和隊列 (queue)
。它們是一種收集數據的有序集合(Collection
),只不過刪除和訪問數據的順序不一樣。數組
stack
能夠做爲一疊的意思,這個排列是垂直的,你將一張紙放在另一張紙上面,先放的紙確定是最後纔會被拿走,由於上面有一張紙擋住了它。queue
和現實世界的排隊意思同樣,這個排列是水平的,先排先得。咱們能夠用數據結構:鏈表
(可連續或不連續的將數據與數據關聯起來的結構),或數組
(連續的內存空間,按索引取值) 來實現棧(stack)
和隊列 (queue)
。安全
數組實現:能快速隨機訪問存儲的元素,經過下標index
訪問,支持隨機訪問,查詢速度快,但存在元素在數組空間中大量移動的操做,增刪效率低。數據結構
鏈表實現:只支持順序訪問,在某些遍歷操做中查詢速度慢,但增刪元素快。併發
數組形式的下壓棧,後進先出:app
主要使用可變長數組來實現。數據結構和算法
// 數組棧,後進先出 type ArrayStack struct { array []string // 底層切片 size int // 棧的元素數量 lock sync.Mutex // 爲了併發安全使用的鎖 }
咱們來分析它的各操做。函數
// 入棧 func (stack *ArrayStack) Push(v string) { stack.lock.Lock() defer stack.lock.Unlock() // 放入切片中,後進的元素放在數組最後面 stack.array = append(stack.array, v) // 棧中元素數量+1 stack.size = stack.size + 1 }
將元素入棧,會先加鎖實現併發安全。性能
入棧時直接把元素放在數組的最後面,而後元素數量加 1。性能損耗主要花在切片追加元素上,切片若是容量不夠會自動擴容,底層損耗的複雜度咱們這裏不計,因此時間複雜度爲O(1)
。
func (stack *ArrayStack) Pop() string { stack.lock.Lock() defer stack.lock.Unlock() // 棧中元素已空 if stack.size == 0 { panic("empty") } // 棧頂元素 v := stack.array[stack.size-1] // 切片收縮,但可能佔用空間愈來愈大 //stack.array = stack.array[0 : stack.size-1] // 建立新的數組,空間佔用不會愈來愈大,但可能移動元素次數過多 newArray := make([]string, stack.size-1, stack.size-1) for i := 0; i < stack.size-1; i++ { newArray[i] = stack.array[i] } stack.array = newArray // 棧中元素數量-1 stack.size = stack.size - 1 return v }
元素出棧,會先加鎖實現併發安全。
若是棧大小爲0,那麼不容許出棧,不然從數組的最後面拿出元素。
元素取出後:
stack.array[0 : stack.size-1]
,代表最後的元素已經不屬於該數組了,數組變相的縮容了。此時,切片被縮容的部分並不會被回收,仍然佔用着空間,因此空間複雜度較高,但操做的時間複雜度爲:O(1)
。newArray
,而後把老數組的元素複製到新數組,就不會佔用多餘的空間,但移動次數過多,時間複雜度爲:O(n)
。最後元素數量減一,並返回值。
// 獲取棧頂元素 func (stack *ArrayStack) Peek() string { // 棧中元素已空 if stack.size == 0 { panic("empty") } // 棧頂元素值 v := stack.array[stack.size-1] return v }
獲取棧頂元素,但不出棧。和出棧同樣,時間複雜度爲:O(1)
。
// 棧大小 func (stack *ArrayStack) Size() int { return stack.size } // 棧是否爲空 func (stack *ArrayStack) IsEmpty() bool { return stack.size == 0 }
一目瞭然,時間複雜度都是:O(1)
。
func main() { arrayStack := new(ArrayStack) arrayStack.Push("cat") arrayStack.Push("dog") arrayStack.Push("hen") fmt.Println("size:", arrayStack.Size()) fmt.Println("pop:", arrayStack.Pop()) fmt.Println("pop:", arrayStack.Pop()) fmt.Println("size:", arrayStack.Size()) arrayStack.Push("drag") fmt.Println("pop:", arrayStack.Pop()) }
輸出:
size: 3 pop: hen pop: dog size: 1 pop: drag
鏈表形式的下壓棧,後進先出:
// 鏈表棧,後進先出 type LinkStack struct { root *LinkNode // 鏈表起點 size int // 棧的元素數量 lock sync.Mutex // 爲了併發安全使用的鎖 } // 鏈表節點 type LinkNode struct { Next *LinkNode Value string }
咱們來分析它的各操做。
// 入棧 func (stack *LinkStack) Push(v string) { stack.lock.Lock() defer stack.lock.Unlock() // 若是棧頂爲空,那麼增長節點 if stack.root == nil { stack.root = new(LinkNode) stack.root.Value = v } else { // 不然新元素插入鏈表的頭部 // 原來的鏈表 preNode := stack.root // 新節點 newNode := new(LinkNode) newNode.Value = v // 原來的鏈表連接到新元素後面 newNode.Next = preNode // 將新節點放在頭部 stack.root = newNode } // 棧中元素數量+1 stack.size = stack.size + 1 }
將元素入棧,會先加鎖實現併發安全。
若是棧裏面的底層鏈表爲空,代表沒有元素,那麼新建節點並設置爲鏈表起點:stack.root = new(LinkNode)
。
不然取出老的節點:preNode := stack.root
,新建節點:newNode := new(LinkNode)
,而後將原來的老節點連接在新節點後面:newNode.Next = preNode
,最後將新節點設置爲鏈表起點stack.root = newNode
。
時間複雜度爲:O(1)
。
// 出棧 func (stack *LinkStack) Pop() string { stack.lock.Lock() defer stack.lock.Unlock() // 棧中元素已空 if stack.size == 0 { panic("empty") } // 頂部元素要出棧 topNode := stack.root v := topNode.Value // 將頂部元素的後繼連接鏈上 stack.root = topNode.Next // 棧中元素數量-1 stack.size = stack.size - 1 return v }
元素出棧。若是棧大小爲0,那麼不容許出棧。
直接將鏈表的第一個節點topNode := stack.root
的值取出,而後將表頭設置爲鏈表的下一個節點:stack.root = topNode.Next
,至關於移除了鏈表的第一個節點。
時間複雜度爲:O(1)
。
// 獲取棧頂元素 func (stack *LinkStack) Peek() string { // 棧中元素已空 if stack.size == 0 { panic("empty") } // 頂部元素值 v := stack.root.Value return v }
獲取棧頂元素,但不出棧。和出棧同樣,時間複雜度爲:O(1)
。
// 棧大小 func (stack *LinkStack) Size() int { return stack.size } // 棧是否爲空 func (stack *LinkStack) IsEmpty() bool { return stack.size == 0 }
func main() { linkStack := new(LinkStack) linkStack.Push("cat") linkStack.Push("dog") linkStack.Push("hen") fmt.Println("size:", linkStack.Size()) fmt.Println("pop:", linkStack.Pop()) fmt.Println("pop:", linkStack.Pop()) fmt.Println("size:", linkStack.Size()) linkStack.Push("drag") fmt.Println("pop:", linkStack.Pop()) }
輸出:
size: 3 pop: hen pop: dog size: 1 pop: drag
隊列先進先出,和棧操做順序相反,咱們這裏只實現入隊,和出隊操做,其餘操做和棧同樣。
// 數組隊列,先進先出 type ArrayQueue struct { array []string // 底層切片 size int // 隊列的元素數量 lock sync.Mutex // 爲了併發安全使用的鎖 }
// 入隊 func (queue *ArrayQueue) Add(v string) { queue.lock.Lock() defer queue.lock.Unlock() // 放入切片中,後進的元素放在數組最後面 queue.array = append(queue.array, v) // 隊中元素數量+1 queue.size = queue.size + 1 }
直接將元素放在數組最後面便可,和棧同樣,時間複雜度爲:O(n)
。
// 出隊 func (queue *ArrayQueue) Remove() string { queue.lock.Lock() defer queue.lock.Unlock() // 隊中元素已空 if queue.size == 0 { panic("empty") } // 隊列最前面元素 v := queue.array[0] /* 直接原位移動,但縮容後繼的空間不會被釋放 for i := 1; i < queue.size; i++ { // 從第一位開始進行數據移動 queue.array[i-1] = queue.array[i] } // 原數組縮容 queue.array = queue.array[0 : queue.size-1] */ // 建立新的數組,移動次數過多 newArray := make([]string, queue.size-1, queue.size-1) for i := 1; i < queue.size; i++ { // 從老數組的第一位開始進行數據移動 newArray[i-1] = queue.array[i] } queue.array = newArray // 隊中元素數量-1 queue.size = queue.size - 1 return v }
出隊,把數組的第一個元素的值返回,並對數據進行空間挪位,挪位有兩種:
queue.array[i-1] = queue.array[i]
,而後數組縮容:queue.array = queue.array[0 : queue.size-1]
,可是這樣切片縮容的那部份內存空間不會釋放。時間複雜度是:O(n)
。
隊列先進先出,和棧操做順序相反,咱們這裏只實現入隊,和出隊操做,其餘操做和棧同樣。
// 鏈表隊列,先進先出 type LinkQueue struct { root *LinkNode // 鏈表起點 size int // 隊列的元素數量 lock sync.Mutex // 爲了併發安全使用的鎖 } // 鏈表節點 type LinkNode struct { Next *LinkNode Value string }
// 入隊 func (queue *LinkQueue) Add(v string) { queue.lock.Lock() defer queue.lock.Unlock() // 若是棧頂爲空,那麼增長節點 if queue.root == nil { queue.root = new(LinkNode) queue.root.Value = v } else { // 不然新元素插入鏈表的末尾 // 新節點 newNode := new(LinkNode) newNode.Value = v // 一直遍歷到鏈表尾部 nowNode := queue.root for nowNode.Next != nil { nowNode = nowNode.Next } // 新節點放在鏈表尾部 nowNode.Next = newNode } // 隊中元素數量+1 queue.size = queue.size + 1 }
將元素放在鏈表的末尾,因此須要遍歷鏈表,時間複雜度爲:O(n)
。
// 出隊 func (queue *LinkQueue) Remove() string { queue.lock.Lock() defer queue.lock.Unlock() // 隊中元素已空 if queue.size == 0 { panic("empty") } // 頂部元素要出隊 topNode := queue.root v := topNode.Value // 將頂部元素的後繼連接鏈上 queue.root = topNode.Next // 隊中元素數量-1 queue.size = queue.size - 1 return v }
鏈表第一個節點出隊便可,時間複雜度爲:O(1)
。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。