news 2026/7/23 22:34:39

Golang学习-栈(Stack)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Golang学习-栈(Stack)

栈(Stack)

一、栈的核心概念

栈是一种后进先出(LIFO, Last In First Out)的线性数据结构。可以把它想象成一摞盘子:你只能在顶部放盘子和取盘子,最先放进去的盘子最后才能取出来。

栈只有一端开放(称为栈顶 Top),所有操作都发生在这一端:

  • Push(入栈):将元素放到栈顶
  • Pop(出栈):移除并返回栈顶元素
  • Peek(查看栈顶):返回栈顶元素但不移除

栈的特性

操作时间复杂度
Push 入栈O(1)
Pop 出栈O(1)
Peek 查看栈顶O(1)
查找O(n)
空间复杂度O(n)

栈的典型应用场景

  1. 函数调用栈:程序运行时的函数嵌套调用就是栈结构
  2. 括号匹配:编译器检查()[]{}是否配对
  3. 表达式求值:后缀表达式(逆波兰表示法)的计算
  4. 浏览器后退:将访问历史压栈,后退时弹栈
  5. DFS 深度优先搜索:用栈替代递归实现
  6. 撤销操作(Undo):每次操作压栈,撤销时弹栈

二、两种实现方式

栈可以用数组(或 Go 切片)或链表来实现,两种方式各有优劣:

2.1 基于切片(数组)的实现

利用 Go 切片的动态扩容特性,栈顶就是切片末尾。

优点

  • 实现简单,代码量少
  • 内存连续,CPU 缓存命中率高
  • Go 切片自动管理扩容

缺点

  • 扩容时需要拷贝数据(均摊 O(1))
  • 大量 Push 可能触发多次扩容

2.2 基于链表的实现

栈顶是链表头节点,Push 做头插,Pop 做头删。

优点

  • 不需要预分配内存,按需分配
  • 没有扩容拷贝开销
  • 每个 Push/Pop 都是严格的 O(1)

缺点

  • 每个节点额外存储指针,内存开销更大
  • 指针跳转导致缓存不友好

三、基于切片的栈实现

packagemainimport"fmt"// ArrayStack 基于切片的栈typeArrayStackstruct{data[]int}funcNewArrayStack()*ArrayStack{return&ArrayStack{data:make([]int,0),}}// Push 入栈 O(1) 均摊func(s*ArrayStack)Push(valint){s.data=append(s.data,val)}// Pop 出栈 O(1) 均摊func(s*ArrayStack)Pop()(int,bool){iflen(s.data)==0{return0,false}index:=len(s.data)-1val:=s.data[index]s.data=s.data[:index]returnval,true}// Peek 查看栈顶 O(1)func(s*ArrayStack)Peek()(int,bool){iflen(s.data)==0{return0,false}returns.data[len(s.data)-1],true}// IsEmpty 判空func(s*ArrayStack)IsEmpty()bool{returnlen(s.data)==0}// Size 返回栈大小func(s*ArrayStack)Size()int{returnlen(s.data)}funcmain(){stack:=NewArrayStack()// 入栈 1,2,3stack.Push(1)stack.Push(2)stack.Push(3)fmt.Println("栈大小:",stack.Size())// 3fmt.Println("栈顶元素:",mustPeek(stack))// 3// 出栈val,_:=stack.Pop()fmt.Println("出栈:",val)// 3val,_=stack.Pop()fmt.Println("出栈:",val)// 2fmt.Println("栈大小:",stack.Size())// 1// 括号匹配检测fmt.Println("\n--- 括号匹配检测 ---")fmt.Println("\"(a+b)*[c-d]\" 匹配?",isBracketMatch("(a+b)*[c-d]"))// truefmt.Println("\"(a+b]*[c-d)\" 匹配?",isBracketMatch("(a+b]*[c-d)"))// falsefmt.Println("\"((()))\" 匹配?",isBracketMatch("((()))"))// truefmt.Println("\"(()\" 匹配?",isBracketMatch("(()"))// false}funcmustPeek(s*ArrayStack)int{v,_:=s.Peek()returnv}

运行结果

栈大小: 3 栈顶元素: 3 出栈: 3 出栈: 2 栈大小: 1 --- 括号匹配检测 --- "(a+b)*[c-d]" 匹配? true "(a+b]*[c-d)" 匹配? false "((()))" 匹配? true "(()" 匹配? false

四、基于链表的栈实现

packagemainimport"fmt"// stackNode 链表栈节点typestackNodestruct{dataintnext*stackNode}// LinkedStack 基于链表的栈typeLinkedStackstruct{top*stackNode// 栈顶指针,指向链表头lenint}funcNewLinkedStack()*LinkedStack{return&LinkedStack{}}// Push 入栈 O(1) — 头插法func(s*LinkedStack)Push(valint){s.top=&stackNode{data:val,next:s.top}s.len++}// Pop 出栈 O(1) — 头删法func(s*LinkedStack)Pop()(int,bool){ifs.top==nil{return0,false}val:=s.top.data s.top=s.top.next s.len--returnval,true}// Peek 查看栈顶 O(1)func(s*LinkedStack)Peek()(int,bool){ifs.top==nil{return0,false}returns.top.data,true}func(s*LinkedStack)IsEmpty()bool{returns.top==nil}func(s*LinkedStack)Size()int{returns.len}funcmain(){stack:=NewLinkedStack()stack.Push(100)stack.Push(200)stack.Push(300)fmt.Println("栈大小:",stack.Size())// 3fmt.Println("栈顶:",mustPeek2(stack))// 300for!stack.IsEmpty(){val,_:=stack.Pop()fmt.Println("出栈:",val)// 300, 200, 100}}funcmustPeek2(s*LinkedStack)int{v,_:=s.Peek()returnv}

运行结果

栈大小: 3 栈顶: 300 出栈: 300 出栈: 200 出栈: 100

五、实战应用:括号匹配检测

括号匹配是栈最经典的应用。算法思路:

  1. 遍历字符串的每个字符
  2. 遇到左括号([{→ 压入栈中
  3. 遇到右括号)]}→ 弹出栈顶元素,检查是否匹配
  4. 遍历结束后,栈应为空
// isBracketMatch 检查字符串中的括号是否匹配funcisBracketMatch(sstring)bool{stack:=NewArrayStack()pairs:=map[rune]rune{')':'(',']':'[','}':'{',}for_,ch:=ranges{switchch{case'(','[','{':stack.Push(int(ch))case')',']','}':top,ok:=stack.Pop()if!ok||top!=int(pairs[ch]){returnfalse}}}returnstack.IsEmpty()}

算法分析

  • 时间复杂度:O(n),每个字符最多入栈出栈一次
  • 空间复杂度:O(n),最坏情况全部是左括号

六、实战应用:用栈实现队列

队列是 FIFO 结构,而栈是 LIFO 结构。用两个栈可以模拟一个队列:

  • 入队:直接压入 input 栈
  • 出队:如果 output 栈为空,将 input 栈所有元素依次弹出并压入 output 栈(此时顺序反转),然后从 output 栈弹出
// StackQueue 双栈实现队列typeStackQueuestruct{inStack*ArrayStack outStack*ArrayStack}funcNewStackQueue()*StackQueue{return&StackQueue{inStack:NewArrayStack(),outStack:NewArrayStack(),}}// Enqueue 入队 — 直接压入 inStackfunc(q*StackQueue)Enqueue(valint){q.inStack.Push(val)}// Dequeue 出队 — 从 outStack 弹出,空则倒灌func(q*StackQueue)Dequeue()(int,bool){ifq.outStack.IsEmpty(){// 将 inStack 所有元素倒入 outStackfor!q.inStack.IsEmpty(){val,_:=q.inStack.Pop()q.outStack.Push(val)}}returnq.outStack.Pop()}

每个元素最多被 Push/Pop 两次(一次进 inStack,一次进 outStack),均摊时间复杂度 O(1)。

七、两种实现对比

维度切片实现链表实现
PushO(1) 均摊O(1) 严格
PopO(1) 均摊O(1) 严格
内存连续性好(缓存友好)差(指针跳转)
扩容开销有(拷贝)
额外内存每节点一个指针
代码复杂度
适用场景通用、数据量可控数据量极大或不确定

实践建议:日常开发中优先用切片实现,简单高效。只有当数据量极大且 Push 频率非常高(扩容拷贝成为瓶颈)时,才考虑链表实现。Go 标准库没有提供内置的 Stack 类型,但切片本身就足够好用。

八、总结

栈是一种"受限"的线性结构——只允许在栈顶操作。正是这个限制,赋予了它 LIFO 的特性和 O(1) 的 push/pop 效率。理解栈的关键在于:

  1. LIFO 特性:最后放入的最先取出
  2. 两种实现:切片(简单高效)和链表(严格 O(1))
  3. 核心应用:括号匹配、表达式求值、DFS、函数调用、Undo/Redo
  4. 双栈技巧:两个栈可以模拟队列,体现了"用受限工具构建复杂结构"的思维
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 22:31:22

这个 App 明天就要上线了

「审核已通过。」林川坐在工位上,看着 App Store Connect 里那行绿色的小字。 周围是下午四点钟寻常的办公室噪音——键盘声、空调声、隔壁运营在打电话。 他截图,发到群里。 群里炸了。「川哥牛逼!」 「终于!!&#x…

作者头像 李华
网站建设 2026/7/23 22:15:47

【Springboot毕设全套源码+文档】基于springboot高校竞赛管理系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/7/23 22:11:24

智能体记忆系统架构与上下文管理技术解析

1. 智能体中的记忆与上下文管理概述在构建复杂智能体系统时,记忆与上下文管理是决定其行为连贯性和决策质量的核心机制。不同于传统程序化的固定响应模式,现代智能体需要像人类一样具备持续学习和情境适应的能力。这就像一位经验丰富的客服人员&#xff…

作者头像 李华
网站建设 2026/7/23 22:01:48

零基础、在职学科莱特SAP培训、五个月从车间计划员到SAP顾问

“我们的称谓基本上都是高老师、高顾问的一个状态,这种生活给带来的这种优越感、这种自信还是有的。”面对科莱特学员回访的镜头,小高说这句话时语气平静,但字里行间透着一种很难伪装的笃定。他1987年出生,大连人,在制…

作者头像 李华
网站建设 2026/7/23 21:56:56

TVA驱动的具身智能迭代逻辑(17)

前沿技术探索:AI智能体视觉(TVA,Transformer-based Vision Agent)是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术,是集深度强化学习(DRL)、卷积神经网络(CNN…

作者头像 李华