栈
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
特点:先进后出
常用方法
- Peek():返回栈顶元素
- Pop():弹出栈顶元素
- Push():将元素入栈
- Size():栈的元素个数
- IsEmpty():栈是否为空
实现
根据栈结构定义,我们发现主要需要解决如下问题
维护一个栈顶元素
栈顶元素需要和前一个元素关联
栈元素个数
根据上述我们可以使用指针属性来关联或者使用切片这种物理关联
使用指针
// 基础结构体 使用泛型实现
type stack[T any] struct {
top *node[T] // 栈顶元素
size int // 栈的元素个数
}
// 栈内元素,包括值、和前一元素的地址
type node[T any] struct {
val T
pre *node[T]
}
// 构造方法
func NewStack[T any]() *stack[T] {
return &stack[T]{
top: nil,
size: 0,
}
}
// 入栈
func (s *stack[T]) Push(val T) {
// 创建新元素节点,新元素的前一节点是当前栈的栈顶元素节点
newNode := &node[T]{val: val, pre: s.top}
// 修改栈的栈顶元素为新元素节点
s.top = newNode
// 修改栈的大小
s.size++
}
// 弹栈, 返回参数可以不包含ok
func (s *stack[T]) Pop() (val T, ok bool) {
// 判断栈的栈顶元素是否为空,或者判断元素个数是否为0
if s.top == nil {
return
}
// 取值
val = s.top.val
// 栈顶元素移动
s.top = s.top.pre
ok = true
// 大小减少
s.size--
return
}
// 获取栈顶元素
func (s *stack[T]) Peek() (val T, ok bool) {
if s.top == nil {
return
}
val, ok = s.top.val, true
return
}
func (s *stack[T]) Size() int {
return s.size
}
func (s *stack[T]) IsEmpty() bool {
return s.size == 0
}
使用切片
type stack[T any] struct {
slice []T // 栈元素队列
index int // 栈的顶元素的索引
}
// 构造方法
func NewStack[T any]() *stack[T] {
return &stack[T]{
top: nil,
index: -1,
}
}
// 入栈,由于是切片会发生扩容
func (s *stack[T]) Push(val T) {
s.slice = append(s.slice, val)
s.index++
}
// 弹栈, 返回参数可以不包含ok
func (s *stack[T]) Pop() (val T, ok bool) {
// 判断栈顶元素的索引是否为-1
if s.index == -1{
return
}
val = s.slice[s.index]
s.slice = s.slice[:index]
s.index--
return
}
// 获取栈顶元素
func (s *stack[T]) Peek() (val T, ok bool) {
// 判断栈顶元素的索引是否为-1
if s.index == -1{
return
}
val, ok = s.slice[s.index], true
return
}
func (s *stack[T]) Size() int {
return len(s.slice)
}
func (s *stack[T]) IsEmpty() bool {
return s.index == -1
}
对比
对比发现,使用切片代码简单,但是由于会发生扩容,所以性能相对于链表来说不是很好,但是如果我们可以确定栈的容量,那么我们就可以考虑使用切片的栈
切片栈的简单实现
// 直接初始化
stack := make([]int, n)
// 栈顶元素索引,-1代表没有
index := -1
// 入栈 Push(val T)
stack[index+1]=val
index++
// 弹栈 Pop()
val = stack[index]
index--
// 获取栈顶元素 Peek()
val = stack[index]
// Size()
size = index+1
// IsEmpty()
index == -1
golang自带实现
type name []type
func (s *name) Push(item type) {
*s = append(*s, item)
}
func (s *name) Pop() (item type) {
if len(*s) == 0 {
return
}
*s, item = (*s)[:len(*s)-1], (*s)[len(*s)-1]
return item
}