写在开头
距离上篇文章已经二十多天了,本来信誓旦旦的说要周更的,果然打脸了,哈哈
由于要看论文,还要兼顾实验室项目新功能的开发和维护,还有恶心的网课和作业,更糟糕的是我最近迷上了吃鸡,就把这事给耽搁了。。。。。。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
为什么我们跳过了双链表,直接来讲栈呢???
因为栈的一种实现方式就是单链表(另一种是数组,后面我会给出伪代码),因此我们在讲完单链表的基础上可以直接来学习栈。
什么是栈呢?
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。----来自百度百科的描述
上面的描述是不是很晦涩呢?我们应该都见过汉诺塔,就是下图的样子(图片来自百度,侵权的话请联系删除哦),它就是一种很经典的堆栈结构,我们最先放入的圈最后才能取出,最后放入的圈就可以直接取出来,这样就很容易理解了吧
这样对堆栈是不是就有了一个很清晰的认识了呢
开始步入正题!
对于go语言我们应该怎么使用代码描述一个栈呢?
因为栈的操作主要就是频繁的入栈和出栈两部分,而在单链表头部的添加和删除节点都是O(1)的复杂度,非常方便,因此,就把栈当作一个只能在头部进行操作的单链表就行了。(这一部分一定要理解)
那么如果我们用单链表实现堆栈,是一种怎样的结构呢????
我们来初始化一个栈,也就是一个单链表
//定义节点
type Node struct {
e interface{}
next *Node
}
//定义栈的结构
type LStack struct {
length int
top *Node
}
//初始化栈
func (s *LStack) InitLStack() *LStack{
return &LStack{
length: 0,
top: nil,
}
}
入栈操作,也就是在单链表的头部插入一个节点
//判断栈是否为空
func (s *LStack) IsEmpty() bool{
return s.length == 0
}
//入栈
func (s *LStack) Push(e interface{}) {
node := &Node{
e ,
nil,
}
//如果栈为空,表示第一次入栈,此时栈顶指向该node
if s.IsEmpty() {
s.top = node
} else {
node.next = s.top
s.top = node
}
s.length++ //栈的长度+1
}
出栈操作,就是删除单链表的头节点
//出栈,返回栈顶的e元素
func (s *LStack) Pop() interface{}{
if s.IsEmpty() {
return nil
}
//将栈顶的top指向top的下一个元素
curNode := s.top
s.top = curNode.next
s.length--
return curNode.e
}
输出所有的栈中的元素
func (s *LStack) PrintLStack(){
curNode := s.top
for {
if curNode != nil {
fmt.Println(curNode.e)
curNode = curNode.next
} else {
break
}
}
}
我们来测试一下
func main() {
s := &LStack{}
s.InitLStack()
s.Push(1)
s.Push(2)
s.Push(3)
s.PrintLStack()
fmt.Println("--------------------")
s.Pop()
s.PrintLStack()
}
输出的结果正确。
其他的功能小伙伴可以自己尝试一下实现呢,比如查询某个值是否在栈中,清空栈等等一系列操作。
好了,这次就到这了,不知道下次写博客会是啥时候呢,希望小伙伴关注一下,给我点动力,哈哈
如果哪里有问题欢迎评论区指出来,我立马改正,一起进步嘛