go 隊列&棧(雙向鏈表實現)

本篇記錄一下go語言自己實現的隊列&棧。

本人的情況是這樣的,上學的時候用的C++,所以秋招也用的C++,結果籤的工作是用go的。。。

用C++的人都知道標準模板庫,比如queue,stack這些,用起來很方便,但是go是不提供這些的,所以對於我來說,感覺很不方便,於是乎就想自己寫個隊列和棧。

在網上搜了搜,實現隊列和棧一般有兩種方法,第一種用slice,我看leetcode上一些題解也是用這種方法取代的隊列或者棧,但顯然,這個方法效率不高,比如下面這個出隊列的寫法,爲了刪掉一個元素,要把整個數組拷貝一次:

queue := queue[1:]

所以我更傾向於第二種方法,就是用go sdk裏面提供的container/list包,這個包實現了一個雙向鏈表,頭插尾插頭刪尾刪複雜度都是O(1),我覺得用它來實現隊列或者棧,比用slice更好一些。

隊列

首先確定一下目標:

  • 隊列可以存儲任何類型(我們自然不是想寫一個只能存int的隊列)
  • 支持以下方法(其實就是用C++刷題的時候常用的那幾個):
    • Size():返回隊列大小
    • Empty():返回隊列是否爲空
    • Front():返回隊列頭結點
    • Back():返回隊列尾節點
    • Pop():刪除隊列頭結點
    • Push():刪除隊列尾節點
  • 協程安全(就是確保多個協程同時訪問的時候不會出問題)

數據結構

首先,定義隊列的數據結構。

type secureQueue struct {
	data *list.List
	size int
	lock chan int8
}

這裏三個變量:

(1)data:go語言自帶的雙向鏈表,源碼在container/list包裏(代碼很簡單,普通人可以看懂的那種),注意這裏是一個指針,因爲後面我們要用到list的方法,對list的內容進行修改;

(2)size:隊列裏面的元素個數,其實也可以直接用list的size方法,但是這樣寫我覺得更清楚一些;

(3)lock:用於實現互斥鎖的channel。

創建函數

我們調用的時候需要先創建一個隊列類型的變量,所以首先實現一個New方法。

func NewSecureQueue() *secureQueue {
	q := new(secureQueue)
	q.init()
	return q
}

func (q *secureQueue) init() {
	q.data = list.New()
	q.lock = make(chan int8, 1)
}

這裏給了兩個函數:

(1)NewSecureQueue方法:使用者調用這個方法,就可以得到一個已經初始化的隊列指針;

(2)init方法:調用list包的New方法獲得一個雙向鏈表,並初始化隊列數據結構中的鎖。

Size方法

直接返回size變量的值。

func (q *secureQueue) Size() int {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.size
}

Empty方法

判斷size是否爲0。

func (q *secureQueue) Empty() bool {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.size == 0
}

Front方法

返回list的頭結點的值。

func (q *secureQueue) Front() interface{} {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.data.Front().Value
}

Back方法

返回list尾節點的值。

func (q *secureQueue) Back() interface{} {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.data.Back().Value
}

Push方法

將一個對象插入list尾部,size+1。

func (q *secureQueue) Push(value interface{}) {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	q.data.PushBack(value)
	q.size++
}

Pop方法

如果隊列不爲空,則將一個list頭結點刪除,size-1。

func (q *secureQueue) Pop() {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	if q.size > 0 {
		tmp := q.data.Back()
		q.data.Remove(tmp)
		q.size--
	}
}

測試

注意一點,我們入隊出隊,用的都是interface,這就保證了,基本上什麼類型的數據都可以插進去,但是這個還是不像C++,可以在定義的時候確定類型,像下面這樣,直接定義一個int型隊列:

queue<int>

所以用的時候,我們得自己判斷類型。

下面測試一下用這個隊列存二叉樹結點。

package main

import (
	"fmt"
	"github.com/djq8888/goQueue"
)

type TreeNode struct {
	Val int
	Left *TreeNode
	Right *TreeNode
}

func main()  {
	q := goQueue.NewSecureQueue()
	fmt.Println(q.Size())
	q.Push(TreeNode{1,nil, nil})
	q.Push(TreeNode{2,nil, nil})
	q.Push(TreeNode{3,nil, nil})
	for !q.Empty() {
		tmp := q.Back().(TreeNode)
		fmt.Println(tmp.Val)
		q.Pop()
	}
}

運行結果如下:

0
3
2
1

恩,還不錯,注意上面我把我的這個包放到github上了(https://github.com/djq8888/goQueue.git),包裏除了有上面的所有代碼,還有一個非協程安全的隊列實現,歡迎大家使用。

跟隊列的實現基本一樣,我就不一個一個說了,直接貼整個代碼。

package goStack

import "container/list"

type secureStack struct {
	data *list.List
	size int
	lock chan int8
}

func NewSecureStack() *secureStack {
	q := new(secureStack)
	q.init()
	return q
}

func (q *secureStack) init() {
	q.data = list.New()
	q.lock = make(chan int8, 1)
}

func (q *secureStack) Size() int {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.size
}

func (q *secureStack) Empty() bool {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.size == 0
}

func (q *secureStack) Top() interface{} {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	return q.data.Back().Value
}

func (q *secureStack) Push(value interface{}) {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	q.data.PushBack(value)
	q.size++
}

func (q *secureStack) Pop() {
	q.lock <- 1
	defer func(lock chan int8) {<- lock}(q.lock)
	if q.size > 0 {
		tmp := q.data.Back()
		q.data.Remove(tmp)
		q.size--
	}
}

同樣的,棧我也實現了非協程安全的版本,代碼也在我的github上,https://github.com/djq8888/goStack.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章