ssm插入数据时候栈溢出_一篇图文彻底搞懂栈

一篇图文彻底搞懂栈

定义

维基百科上对栈的说明如下示:
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素

简单定义:栈就是一种只允许在表尾进行插入和删除操作的线性表

也可以这么理解:
先进后出,先进队的数据最后才出来。在英文的意思里,stack 可以作为一叠的意思,这个排列是垂直的,你将一张纸放在另外一张纸上面,先放的纸肯定是最后才会被拿走,因为上面有一张纸挡住了它。

上面说半天定义可能也不是很直观的能想象到栈到底是个啥玩意,话不多说,直接上图:

6d88ff8fb1d20952e581aebf39698840.png

a937f57a7d63cddf6bc0fe75156de420.png

怎么更加形象的理解呢,举一个生活中的例子:我在一个储物箱中,堆了一堆衣服,我的一件球衣在最下面,而我要拿这件衣服,就意味着我必须将上面的衣服全部拿出来才可以,但是由于箱子只有一个口,我也只能从上面拿东西,心里还默默想着,当初就不该将球衣早早的放进去,导致结果就是先进后出!这其实就是栈。

栈的专业术语说明

  • 栈顶:允许进行插入和进行删除操作的一段成为栈顶
  • 栈底:表的另一端称为栈底 (第一个元素进入的位置
  • 压栈(入栈、进栈):在栈顶位置插入元素的操作叫做压栈,或入栈、进栈
  • 出栈(弹栈、退栈):删除栈顶元素的操作叫做出栈,也叫作弹栈,或者退栈
  • 空栈:不含元素的空表
  • 栈溢出:当栈满的时候,如果再有元素压栈,则发生上溢,当栈空的时候,再出栈则发生下溢

如何实现

我们可以用数据结构:链表(可连续或不连续的将数据与数据关联起来的结构),或 数组(连续的内存空间,按索引取值) 来实现 栈(stack)。

  • 数组实现 能快速随机访问存储的元素,通过下标 index 访问,支持随机访问,查询速度快,但存在元素在数组空间中大量移动的操作,增删效率低。
  • 链表实现 只支持顺序访问,在某些遍历操作中查询速度慢,但增删元素快。

本章我们也将用数组和链表两种形式实现栈。

栈的实现

首先,我们定义一下栈操作的基本接口:

// 定义栈接口
type Stack interface {
 Push(v interface{})   //入栈
 Pop() (v interface{}) //出栈
 IsEmpty() (ok bool)   //判断是否为空
 Top() (v interface{}) //获取栈顶元素
 Reset()               //重置栈
 Print()               //打印栈
}

顺序栈

栈是线性表的特例,所以栈的顺序存储结构其实就是线性表顺序存储结构的简称,我们简称为顺序栈。线性表是用数组来实现的,对于栈这种只能一头插入删除的线性表来说,用数组下标为0(栈底不变,只需要跟踪栈顶的变化即可)的一端作为栈底比较合适。

形象的表示图如下所示:

3db47a64c1263f2a3908caa8a262abd9.png

栈操作

我们重点讲解下入栈和出栈操作,直观的示意图如下示:

771b677619fa36babab561ea38828209.png

在golang中,我们可以使用切片来实现顺序栈,定义如下示:

type StackOnArray struct {
 data []interface{} //栈的元素列表
 size int           // 栈的元素数目
 mu   sync.Mutex    // 为了并发安全使用的锁
}

入栈

结合上面形象的示意图来理解,入栈操作很简单,用代码表示如下示:

func (this *StackOnArray) Push(v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 this.data = append(this.data, v)
 this.size++
 return
}

入栈时直接把元素放在数组的最后面,然后元素数量加 1。性能损耗主要花在切片追加元素上,切片如果容量不够会自动扩容,底层损耗的复杂度我们这里不计,所以时间复杂度为 O(1)。

出栈

结合上面形象的示意图,那么出栈操作也很easy,用代码表示如下示:

func (this *StackOnArray) Pop() (v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.IsEmpty() {
  return nil
 }
 v = this.data[this.size-1]
 this.data = this.data[0 : this.size-1]
 this.size--
 return v
}

如果栈大小为0,那么不允许出栈,否则从数组的最后面拿出元素。

其余操作也很简单了,下面我们直接给出完整的代码示例

完整的代码示例

package stack

import (
 "fmt"
 "sync"
)

/*
 * 基于数组实现的栈
 */
type StackOnArray struct {
 data []interface{} //栈的元素列表
 size int           // 栈的元素数目
 mu   sync.Mutex    // 为了并发安全使用的锁
}

func NewStackOnArray() *StackOnArray {
 return &StackOnArray{}
}

func (this *StackOnArray) Push(v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 this.data = append(this.data, v)
 this.size++
 return
}

func (this *StackOnArray) Pop() (v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.IsEmpty() {
  return nil
 }
 v = this.data[this.size-1]
 this.data = this.data[0 : this.size-1]
 this.size--
 return v
}

func (this *StackOnArray) IsEmpty() (ok bool) {
 return this.size == 0
}

func (this *StackOnArray) Top() (v interface{}) {
 if this.IsEmpty() {
  return nil
 }
 return this.data[this.Size()-1]
}

func (this *StackOnArray) Reset() {
 this.data = make([]interface{}, 0)
 this.size = 0
 return
}

func (this *StackOnArray) Size() int {
 return this.size
}

func (this *StackOnArray) Print() {
 if this.IsEmpty() {
  fmt.Printf("stack is empty!n")
  return
 }
 fmt.Printf("stack size is:[%v]n", this.size)
 fmt.Printf("print stack elem......n")
 for i := this.size - 1; i >= 0; i-- {
  fmt.Printf("index:%v, elem:[%v]n", i, this.data[i])
 }
 return
}

链表栈

栈顶放在单链表的头部,用链表来存储栈的的数据结构称为链栈。

一个直观的示意图如下示:

adeadeb1596db5b56365cd833e6c3007.png

如果对链表的操作还不是很了解的话,可以参阅我之前的链表讲解部分。

栈操作

这里我们也只重点讲解一下入栈和出栈使用链表如何去实现。

链表形式的数据结构定义如下示:

type LinkNode struct {
 data interface{}
 next *LinkNode
}

type StackOnLinklist struct {
 top  *LinkNode //栈顶元素
 size int       //栈的元素数目
 mu   sync.Mutex
}

进栈

进栈示意图如下示:

6ef6048959ce24a6371c87f7b4eee2c9.png

代码示例如下:

func (this *StackOnLinklist) Push(v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.top == nil {
  node := &LinkNode{data: v}
  this.top = node
 } else {
  preNode := this.top

  newNode := &LinkNode{data: v}
  newNode.next = preNode

  this.top = newNode
 }
 this.size++
 return
}

结合代码和示意图理解如下:

如果栈里面的底层链表为空,表明没有元素,那么新建节点并设置为链表起点:stack.top = new(LinkNode)。

否则取出老的节点:preNode := stack.top,新建节点:newNode := new(LinkNode),然后将原来的老节点链接在新节点后面: newNode.Next = preNode,最后将新节点设置为链表起点 stack.top = newNode。

时间复杂度为:O(1)。

出栈

出栈示意图如下示:

f89fe15075e10f4daa6fc2f4ccc7ec89.png

代码示例如下:

func (this *StackOnLinklist) Pop() (v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.IsEmpty() {
  return nil
 }

 v = this.top.data

 this.top = this.top.next
 this.size--
 return v
}

结合示意图和代码直观讲解:

元素出栈。如果栈大小为0,那么出栈元素为空。

直接将链表的第一个节点 topNode := stack.top 的值取出,然后将表头设置为链表的下一个节点:stack.top = stack.top.Next,相当于移除了链表的第一个节点。

时间复杂度为:O(1)。

完整代码

package stack

import (
 "fmt"
 "sync"
)

type LinkNode struct {
 data interface{}
 next *LinkNode
}

type StackOnLinklist struct {
 top  *LinkNode //栈顶元素
 size int       //栈的元素数目
 mu   sync.Mutex
}

func NewStackOnLinklist() *StackOnLinklist {
 return &StackOnLinklist{
  top:  nil,
  size: 0,
 }
}

func (this *StackOnLinklist) Push(v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.top == nil {
  node := &LinkNode{data: v}
  this.top = node
 } else {
  preNode := this.top

  newNode := &LinkNode{data: v}
  newNode.next = preNode

  this.top = newNode
 }
 this.size++
 return
}

func (this *StackOnLinklist) Pop() (v interface{}) {
 this.mu.Lock()
 defer this.mu.Unlock()

 if this.IsEmpty() {
  return nil
 }

 v = this.top.data

 this.top = this.top.next
 this.size--
 return v
}

func (this *StackOnLinklist) IsEmpty() (ok bool) {
 return this.size == 0
}

func (this *StackOnLinklist) Top() (v interface{}) {
 if this.IsEmpty() {
  return nil
 }
 return this.top.data
}

func (this *StackOnLinklist) Reset() {
 this.size = 0
 this.top = nil
 return
}

func (this *StackOnLinklist) Size() int {
 return this.size
}

func (this *StackOnLinklist) Print() {
 if this.IsEmpty() {
  fmt.Printf("stack is empty!n")
  return
 }
 fmt.Printf("stack size is:[%v]n", this.size)
 fmt.Printf("print stack elem......n")

 curNode := this.top
 for curNode != nil {
  fmt.Printf("elem:[%v]n", curNode.data)
  curNode = curNode.next
 }
 return
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值