Swift 遇上数据结构之栈

2,837 阅读4分钟

一、什么是栈

栈:一种线性数据结构,它的特色就是入栈出栈的方式:后入先出(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
  • 网页的访问顺序
  • 逆波兰表达式

最后

希望看完这篇文章能让你对栈有一个全面的了解。能给你带来收获就是本文最大的价值😺。

本文有何纰漏之处还望在评论区不吝赐教。

完整代码地址