一、什么是栈
栈:一种线性数据结构,它的特色就是入栈出栈的方式:后入先出(LIFO)。在现实生活中也有很多类似于栈的例子,比如在家里盛饭的时候,我们一般会将一摞碗的最上方的碗拿来使用,而最上方的碗一般都会是你最后一个摞上去的。

二、如何用数组实现
对于栈的结构来说和数组很相似,只是一些接口上的不同,下面就用数组来实现一下栈。 首先来看一下需要实现的接口:
- top:返回栈顶元素
- isEmpty:栈是否为空
- count: 栈包含多少元素
- push:入栈
- pop:出栈
1、定义一个 Stack 的结构体
实现上述接口的基本结构。
struct Stack {
public var top: Element?
public var isEmpty: Bool
private var _elements = [Element]()
public func push(_ newElement: Element) {
}
public func pop() -> Element? {
return nil
}
}
2、实现 top、 isEmpty 和 count 接口
top 是返回栈顶元素。也就是最后一个被添加到 _elements 中的元素,即 _elements 的最后一个元素。
public var top: Element? { return _elements.last }
isEmpty 代表栈是否为空,等价于 _elements 是否为空。
public var isEmpty: Bool { return _elements.isEmpty }
count 等价于 _elements 中元素的个数。
public var count: Int { return _elements.count }
3、实现 push 接口
向栈里添加元素。
/// 入栈
/// - Parameter newElement: 添加到栈顶的元素
public mutating func push(_ newElement: Element) {
_elements.append(newElement)
}
4、实现 pop 接口
移除栈顶元素。注意:这里需要移除的并不是 _elements 中的第一个元素,应该是最后一个元素。因为栈是 LIFO。
/// 出栈:移除栈顶元素
/// - Returns: 栈顶元素
public mutating func pop() -> Element? {
return _elements.popLast()
}
5、测试代码
func test() {
var stack = Stack<String>()
assert(stack.isEmpty)
stack.push("Swift")
stack.push("Java")
stack.push("C")
stack.push("C++")
stack.push("PHP")
assert(stack.pop() == "PHP")
assert(stack.count == 4)
assert(stack.pop() == "C++")
assert(stack.pop() == "C")
assert(stack.pop() == "Java")
assert(stack.pop() == "Swift")
assert(stack.isEmpty)
}
6、栈出栈入栈的过程


三、如何不借助数组实现
既然不用数组来放元素,那么我们就得自己实现一个存放元素的容器,下面来实现一下。
定义存放元素的容器: StackBuffer
/// 栈的底层结构(Storage buffer for a Stack)
class StackBuffer<Element> {
// 元素的数量
var count = 0
// 栈顶元素
var top: Element
// 栈的容量
private var _capacity: Int
// 指向元素的内存指针
private var _elements: UnsafeMutablePointer<Element>
// 构造器
init(capacity: Int) { }
// 入栈
func push(_ newElement: Element) { }
// 出栈
func pop() -> Element { }
// 析构器
deinit { }
}
定义好上面的模板,下面就是一步一个的实现它了。先从好搞的开始。
1、实现 top
返回最后一个元素:
var top: Element { return _elements.advanced(by: count - 1).pointee }
2、构造器
因为我们是自己管理内存,所以需要向系统申请一块可使用的内存:
init(capacity: Int) {
_capacity = capacity
_elements = UnsafeMutablePointer.allocate(capacity: capacity)
}
3、入栈
申请完内存后,我们需要给当前位置的内存初始化,并将元素数量加一:
func push(_ newElement: Element) {
_elements.advanced(by: count).initialize(to: newElement)
count += 1
}
4、出栈
将当前位置内存的元素移除并返回移除元素,元素数量减一:
func pop() -> Element {
let last = _elements.advanced(by: count - 1).move()
count -= 1
return last
}
5、析构器
因为我们自己管理内存,所以需要在不适用该块内存时,将其释放掉:
deinit {
// 去初始化该内存
_elements.advanced(by: 0).deinitialize(count: count)
// 释放该内存
_elements.deallocate()
}
用 StackBuffer 实现 Stack
因其代码逻辑与使用数组实现的逻辑一致,这里就直接贴出完整代码了:
public struct Stack<Element> {
/// 栈元素数量 (Number of stack elements)
public var count: Int { return _stackBuffer.count }
/// 栈是否为空 (Is the stack empty)
public var isEmpty: Bool { return _stackBuffer.count == 0 }
/// 栈顶元素 (The top element of stack)
public var top: Element { return _stackBuffer.top }
private var _stackBuffer: StackBuffer<Element>!
public init(capacity: Int) {
_stackBuffer = StackBuffer(capacity: capacity)
}
/// 进栈 (Push)
/// - Parameter newElement: 进栈元素 (The element of push)
public func push(_ newElement: Element) {
_stackBuffer.push(newElement)
}
/// 出栈 (Pop)
/// - Returns: 出栈元素(The element of pop)
public func pop() -> Element {
return _stackBuffer.pop()
}
}
四、栈的用处
学习完一项知识后,我们应该知道它有什么应用,下面就是栈的一些实际应用:
- UINavigationController
- 网页的访问顺序
- 逆波兰表达式
最后
希望看完这篇文章能让你对栈有一个全面的了解。能给你带来收获就是本文最大的价值😺。
本文有何纰漏之处还望在评论区不吝赐教。