golang 数据结构 栈的pop与push

数据实现类似C语言的实现方式。我这里使用两种方式实现 栈的 pop 和 push。

第一种使用 struct 实现

type Node struct {
    value    interface{}
    previous *Node      // 前一个节点
    Next     *Node      //  下一个节点
}

func (sel *Node) Value() interface{} {
    return sel.value
}

func newNode(val interface{}) *Node {
    return &Node{value: val}
}

type Stack struct {
    sync.Mutex
    head   *Node
    rear   *Node
    length int32
}

func NewPool() *Stack {
    return &Stack{}
}
// 出栈,取出链表中的第一个节点, (先进后出)
func (sel *Stack) Pop() *Node {
    sel.Lock()
    defer sel.Unlock()
    val := &Node{
        value: sel.head.value,
    }
    // 头节点指向当前节点的下一个节点
    sel.head =  sel.head.Next
     // 这里使用了 lock 可以不使用 atomic 原子操作
    atomic.AddInt32(&sel.length, -1)
    return val
}

func (sel *Stack) Push(v interface{}) {
    sel.Lock()
    defer sel.Unlock()
    sel.push(v)
}
// 插入新节点到头部
func (sel *Stack) push(v interface{}) {
    top := newNode(v)
    if sel.length == 0 {
        sel.head = top
        sel.rear = sel.head
    }
    // 新节点指向当前头节点
    top.Next = sel.head
    // 当前头节点的前一个节点指向新节点
    sel.head.previous = top
    // 改变当前头节点地址为新节点地址
    sel.head = top
    atomic.AddInt32(&sel.length, 1)
}
// 取出链表中最早的节点(先进先出)
func (sel *Stack) Shift() *Node {
    val := sel.rear
    sel.rear = sel.rear.previous
    sel.rear.Next = nil
    val.Next = nil
    val.previous = nil
    return val
}

func (sel *Stack) Length() int32 {
    return sel.length
}

测试代码:

func TestPool_Push(t *testing.T) {
    po := NewPool()
    num := 100000000
    start := time.Now().UnixNano()
    for i := 0; i < num; i++ {
        po.Push(i)
    }
    end := time.Now().UnixNano()
    fmt.Println("入栈时间:", (end-start)/1e6)
    fmt.Println("长度:", po.Length())
    start = time.Now().UnixNano()
    for i := 0; i < num; i++ {
        po.Pop()
    }
    end = time.Now().UnixNano()
    fmt.Println("出栈时间:", (end-start)/1e6)
}

测试结果:

=== RUN   TestPool_Push
入栈时间: 14638
长度: 100000000
出栈时间: 10500
--- PASS: TestPool_Push (25.14s)
PASS

第二种方法使用 slice 实现 pop 和 push

实现代码如下:

type ItemNode []interface{}

type StackSlice struct {
    items    ItemNode
    length   int
    capacity int
    sync.RWMutex
}

func NewStackSlice(cp int) *StackSlice {
    return &StackSlice{
        items:    make(ItemNode, 0, cp),
        capacity: cp,
    }
}

func (sel *StackSlice) Pop() interface{} {
    sel.Lock()
    defer sel.Unlock()
    length := len(sel.items)
    if length == 0 {
        return nil
    }
    item := sel.items[length-1]
    sel.items = sel.items[:length-1]
    return item
}

func (sel *StackSlice) Push(val interface{}) {
    sel.Lock()
    defer sel.Unlock()
    sel.items = append(sel.items, val)
}

func (sel *StackSlice) Shift() interface{} {
    sel.Lock()
    defer sel.Unlock()
    length := len(sel.items)
    if length == 0 {
        return nil
    }
    item := sel.items[0]
    if length > 1 {
        sel.items = sel.items[1:length]
    } else {
        sel.items = make([]interface{}, 0, sel.capacity)
    }
    return item
}

测试代码:

func TestStackSlice_Push2(t *testing.T) {
    num := 100000000
    po := NewStackSlice(100000000)
    start := time.Now().UnixNano()
    for i := 0; i < num; i++ {
        po.Push(i)
    }
    end := time.Now().UnixNano()
    fmt.Println("入栈时间:", (end-start)/1e6)
    start = time.Now().UnixNano()
    for i := 0; i < num; i++ {
        po.Pop()
    }
    end = time.Now().UnixNano()
    fmt.Println("出栈时间:", (end-start)/1e6)
}

测试结果:

=== RUN   TestStackSlice_Push2
入栈时间: 7312
出栈时间: 4828
--- PASS: TestStackSlice_Push2 (12.28s)
PASS

对于第二种方法,把 capacity 缩短或者改为 0

func TestStackSlice_Push2(t *testing.T) {
    num := 100000000
    // 这里修改 capacity 为  0
    po := NewStackSlice(0)
    start := time.Now().UnixNano()
    for i := 0; i < num; i++ {
        po.Push(i)
    }
    end := time.Now().UnixNano()

    fmt.Println("入栈时间:", (end-start)/1e6)
    start = time.Now().UnixNano()
    for i := 0; i < num; i++ {
        //fmt.Println(po.Shift())
        po.Pop()
    }
    end = time.Now().UnixNano()
    fmt.Println("出栈时间:", (end-start)/1e6)
}

测试结果:

=== RUN   TestStackSlice_Push2
入栈时间: 18999
出栈时间: 4835
--- PASS: TestStackSlice_Push2 (23.83s)
PASS

如果slice 的容量缩短了,时间就会慢很多,因为 slice 容量不够的情况下,会自动扩展容量,并且会有数据拷贝。

总结

性能测试代码如下

var po = NewPool()
var poSlice = NewStackSlice(100000000)

func BenchmarkStack_Push(b *testing.B) {
    for i := 0; i < b.N; i++ {
        po.Push(i)
    }
}

func BenchmarkStack_Pop(b *testing.B) {
    b.StopTimer()

    for i := 0; i < b.N; i++ {
        po.Push(i)
    }
    b.StartTimer()
    for i := 0; i < b.N; i++ {
        po.Pop()
    }
}

func BenchmarkStackSlice_Pop(b *testing.B) {
    for i := 0; i < b.N; i++ {
        poSlice.Push(i)
    }
}

func BenchmarkStackSlice_Push(b *testing.B) {
    b.StopTimer()
    for i := 0; i < b.N; i++ {
        poSlice.Push(i)
    }
    b.StartTimer()
    for i := 0; i < b.N; i++ {
        poSlice.Pop()
    }
}

测试结果: go test -bench=".*" -run=None -benchmem

BenchmarkStack_Push-4           20000000               103 ns/op              40 B/op          1 allocs/op
BenchmarkStack_Pop-4            20000000               132 ns/op              32 B/op          1 allocs/op
BenchmarkStackSlice_Pop-4       20000000                68.9 ns/op             8 B/op          0 allocs/op
BenchmarkStackSlice_Push-4      30000000                49.1 ns/op             0 B/op          0 allocs/op

  • 第一种使用struct实现 pop 和 push,是可以自动扩展且不需要关心容量,但是数据较慢。
  • 第二种使用slice 实现 pop 和 push, 在容量足够的情况下速度是比第一种快了很多,但是如果容量不足会降低入栈的速度。slice 方法实现的出栈速度是不受容量影响的,比struct实现的要快很多。

但是也有网友给出不同的测试结果 https://studygolang.com/articles/19828?fr=sidebar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,144评论 1 32
  • 栈结构 栈也是一种非常常见的数据结构, 并且在程序中的应用非常广泛. 一. 认识栈结构 我们先来简单认识一下栈结构...
    小码哥教育520it阅读 1,939评论 0 1
  • 1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不...
    曲终人散Li阅读 3,371评论 0 19
  • 如需转载, 请咨询作者, 并且注明出处.有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: ...
    coderwhy阅读 19,654评论 5 48
  • 今天是我大显伸手的时候到了,因为爸爸、妈妈都不在家只有我和妹妹在家里,可是我们好饿。怎么办呢?那只好我为妹妹做一顿...
    畅博辉妈妈阅读 492评论 0 0