本篇记录一下go语言自己实现的队列&栈。
本人的情况是这样的,上学的时候用的C++,所以秋招也用的C++,结果签的工作是用go的。。。
用C++的人都知道标准模板库,比如queue,stack这些,用起来很方便,但是go是不提供这些的,所以对于我来说,感觉很不方便,于是乎就想自己写个队列和栈。
在网上搜了搜,实现队列和栈一般有两种方法,第一种用slice,我看leetcode上一些题解也是用这种方法取代的队列或者栈,这种方法的一个缺陷是,如果触发了slice扩容,就会需要对整个底层数组进行一次拷贝。
queue = append(queue, x)
所以我更倾向于第二种方法,就是用go sdk里面提供的container/list包,这个包实现了一个双向链表,头插尾插头删尾删复杂度都是固定O(1),我觉得用它来实现队列或者栈,比用slice更好一些。
首先确定一下目标:
首先,定义队列的数据结构。
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变量的值。
func (q *secureQueue) Size() int {
q.lock <- 1
defer func(lock chan int8) {<- lock}(q.lock)
return q.size
}
判断size是否为0。
func (q *secureQueue) Empty() bool {
q.lock <- 1
defer func(lock chan int8) {<- lock}(q.lock)
return q.size == 0
}
返回list的头结点的值。
func (q *secureQueue) Front() interface{} {
q.lock <- 1
defer func(lock chan int8) {<- lock}(q.lock)
return q.data.Front().Value
}
返回list尾节点的值。
func (q *secureQueue) Back() interface{} {
q.lock <- 1
defer func(lock chan int8) {<- lock}(q.lock)
return q.data.Back().Value
}
将一个对象插入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++
}
如果队列不为空,则将一个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
所以用的时候,我们得自己判断类型。
下面测试一下用这个队列存二叉树结点。
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