go 队列&栈(双向链表实现)

本篇记录一下go语言自己实现的队列&栈。

本人的情况是这样的,上学的时候用的C++,所以秋招也用的C++,结果签的工作是用go的。。。

用C++的人都知道标准模板库,比如queue,stack这些,用起来很方便,但是go是不提供这些的,所以对于我来说,感觉很不方便,于是乎就想自己写个队列和栈。

在网上搜了搜,实现队列和栈一般有两种方法,第一种用slice,我看leetcode上一些题解也是用这种方法取代的队列或者栈,这种方法的一个缺陷是,如果触发了slice扩容,就会需要对整个底层数组进行一次拷贝。

queue = append(queue, x)

所以我更倾向于第二种方法,就是用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

所以用的时候,我们得自己判断类型。

下面测试一下用这个队列存二叉树结点。

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

你可能感兴趣的:(go,数据结构与算法)