链式存储结构最大的好处就是没有空间的限制,通过指针指向将结点像一个链子一样把结点链接,那么栈的同样可以用于链式存储结构。
栈的链式存储结构,简称为链栈。想想看,栈只是栈顶来做插入和删除操作,栈顶放在链表的头部还是尾部呢?由于单链表有头指针,而栈顶指针也是必须的,那么干嘛不让他们合二为一呢,所以比较好的办法是把栈顶放到单链表的头部。另外栈顶在头部了,那么单链表的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。
同样对于链栈来说,基本不存在栈满的情况,除非内存已经没有可用的空间了。
下面是链栈的指向图:
同样链栈中最重要的算法是压栈和弹栈。
压栈操作:
操作很简单,新建结点,给结点数据域赋值,指针域指向原来栈顶元素。
然后将栈顶指针指向新结点,元素个数+1,就ok。如下图:
弹栈操作:
和压栈相反,将栈顶元素的数据域取通过指针返回这个数据,将要弹出的元素的指针域赋值给栈顶指针,然后释放这个元素,这样栈顶指针就指向新的栈顶了,然后元素个-1。如图:
下面我们来看一下相关代码的实现。
代码实例:
package main
import (
"errors"
"fmt"
)
type seleType int
//节点数据类型
type StackNode struct {
data seleType//数据
next *StackNode//指针
}
type LinkStackPtr *StackNode
type LinkStak struct {
top LinkStackPtr//栈顶指针,每次都操作栈顶指针,为节点类型的指针
count int //保存节点的数量
}
//入栈操作
func push1(s *LinkStak, e seleType) {
p := LinkStackPtr(new(StackNode)) //新分配内存空间
p.data = e
p.next = s.top //新添加的数据的指针指向原来栈顶的元素
s.top = p //调整栈顶
s.count++ //调整数量
}
//出栈操作
func pop1(s *LinkStak) (err error, res seleType) {
if s.top == nil {
err = errors.New("栈为空,不能出栈")
return
}
res=s.top.data
p:=s.top
s.top=p.next
s.count--
return
}
func main() {
var link LinkStak
push1(&link,123)
push1(&link,456)
fmt.Println("链表数据节点:",link.count)
_,res:=pop1(&link)
fmt.Println(res)
_,res=pop1(&link)
fmt.Println(res)
err,res:=pop1(&link)
fmt.Println(err,res)
fmt.Println("链表数据节点:",link.count)
}
运行效果:
注:栈的顺序存储结构可以理解为:通过结构体对数组进行操作;栈的链式存储结构可以理解为:通过结构体对链表进行操作。