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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

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