您的当前位置:首页正文

Swift中的栈

来源:华佗小知识

栈(Stack)是一种后入先出(Last in First out,简称LIFO)的数据结构。你可以把它想象成一个羽毛球的收纳筒,最先放进去的那个羽毛球一定是最后拿出来,而最后放进去的那个羽毛球一定是最先拿出来。栈结构和数组非常类似,不过相比而言,栈的访问方式受到了一定的限制。下面是栈数据结构的示意图:

栈结构示意图.png

数组里面的元素是可以随机访问的,而栈结构在数据元素访问接口这一块有着严格的限制。从上图中可以看出,栈结构里面的元素在访问时,只能从一端进行。栈数据结构中主要实现了下面这三种方法:

  • push():将一个元素压入到栈底;
  • pop():从栈顶移除并返回一个元素;
  • peek():从栈顶返回一个元素,但是这个元素不会从栈中删除。

当然,除了上面这三种主要的方法之外,栈中还实现了一些用于辅助操作的方法和属性,主要有下面这几种:

  • cout:用于返回栈中元素的个数;
  • isEmpty():如果栈为空,就返回true,否则就返回false;
  • isFull():如果栈存储空间已满,则返回true,否则返回false。

栈的定义由一系列的接口组成,而这些接口又定义了栈可以执行哪些操作。在实现一个栈时,你并不需要定义底层的数据结构。一般而言,通用的数据结构是由数组或者链表组成,具体采用哪种实现方式,取决项目需求的性能特征。

1、栈的应用

栈常见的应用场景有:表达式评估和语法解析。具体表现为,将整数转换为二进制、回溯算法,以及使用命令设计模式支持撤消和重做功能。

2、栈的实现

在Stack类型的数据结构中,我们将会实现以下几个方法和属性:push()、 pop()、peek()、isEmpty(),以及count属性。因为数组中有很多现成的方法,这对我们Stack类型的实现有极大的帮助, 所以我们会采用数组作为Stack类型的辅助存储器:

// 定义一个Stack类型的栈
public struct Stack<T> {
    
    // 数组作为辅助存储器,主要是用来存储Stack里面的元素
    fileprivate var elements = [T]()
    
    // 构造器
    public init() {}
    
    // 出栈操作:从Stack中删除一个元素,并且将其返回
    public mutating func pop() -> T? {
        return self.elements.popLast()
    }
    
    // 压栈操作:将数据元素压入栈底(类似于数组中的添加元素)
    public mutating func push(element: T){
        self.elements.append(element)
    }
    
    // 出栈操作:从Stack中返回一个数据元素,但是不会将其从Stack中删除
    public func peek() -> T? {
        return self.elements.last
    }
    
    // 判断Stack里面的元素是否为空
    public func isEmpty() -> Bool {
        return self.elements.isEmpty
    }
    
    // 用于获取Stack里面的元素个数
    public var count: Int {
        return self.elements.count
    }
}

在上面的代码中,我们使用了泛型,因为它可以在编译时才确定存储在Stack中的数据究竟是什么类型,这为我们对Stack数据类型的设计提供了很大的灵活性。除此之外,我们还在Stack中使用了数组,用它来存储Stack中的元素。不光如此,数组中还提供了一个popLast()方法,它为我们实现从栈顶删除一个元素提供了很大的帮助。下面我们就来用一下这个Stack数据类型:

// 声明一个Stack类型的变量
var myStack = Stack<Int>()

// 调用压栈方法,将数据存入栈底
myStack.push(element: 5)  // [5]
myStack.push(element: 44)  // [5, 44]
myStack.push(element: 23)  // [5, 44, 23]

// 调用出栈方法,将栈顶的数据删除
var x = myStack.pop()  // x = 23
x = myStack.pop()  // x = 44
x = myStack.pop()  // x = 5

// 如果栈中的元素已经被清空了,再次调用出栈方法会返回nil
x = myStack.pop()  // x = nil

3、通过协议扩展栈

我们只是初步定义了一个Stack类型的栈,距离实际应用还差得很远,接下来我们要用extension对其进行相应的扩展。在上一篇文章中,我们介绍过迭代器(iterators)和序列(sequences),下面我们就要把它们用在我们的Stack类型里。

我们会添加几个便利协议。首先要添加的就是CustomStringConvertible协议和CustomDebugStringConvertible协议。这两个协议允许你在打印Stack类型及其元素值时,返回比较友好的名称:

// 打印Stack类型及其元素的值
extension Stack: CustomStringConvertible, CustomDebugStringConvertible {
    
    // 栈及其元素的文本表示
    public var description: String {
        return self.elements.description
    }
    
    // 栈及其元素的文本表示,主要用于调试
    public var debugDescription: String {
        return self.elements.debugDescription
    }
}

接下来,我们肯定希望Stack在定义的时候,用起来像Array一样的方便,为此我们需要让它遵守ExpressibleByArrayLiteral协议。它可以让我们在声明一个Stack类型时,可以使用快速声明的语法:

// 让Stack能够使用快速声明的语法
extension Stack: ExpressibleByArrayLiteral {
    
    public init(arrayLiteral elements: T...) {
        self.init(elements)
    }
}

// 使用快速声明的语法来初始化一个Stack类型的变量
var stringStack : Stack = ["LeBron James", "Carmelo Anthony", "Dwyane Wade", "Chris Paul"]

使用快速声明语法时一定要注意,必须明确指明它是Stack类型,否则编译器会把它当做普通的Array类型进行处理。接下来我们继续扩展Stack,让它遵守IteratorProtocol协议,从而可以像Array那样使用迭代器:

// 给Stack扩展迭代器
public struct ArrayIterator<T> : IteratorProtocol {
    var currentElement: [T]
    
    init(elements: [T]){
        self.currentElement = elements
    }
    
    mutating public func next() -> T? {
        if (!self.currentElement.isEmpty) {
            return self.currentElement.popLast()
        }
        return nil
    }
}

因为Stack类型在实现时使用了数组作为其内部存储结构,我们定义了一个类型为[T]的变量currentElement,在泛型T定义的位置,当结构体ArrayIterator被使用时,它会将数组存储起来然后传递给构造器。除此之外,我们还实现了负责返回序列中下一个元素的next()方法,如果我们遍历到数组的末尾,它就会返回一个nil,然后通知Swift的运行时,数组中已经没有可遍历的元素了,不必再执行for...in循环了。

接下来,我们希望通过实现makeIterator()方法来继续扩展Stack类型,让Stack类型可以支持for...in循环。为此我们应该遵守Sequence协议。 在这个实现中,需要连接上面定义的ArrayIterator类型:

// 给Stack添加for...in支持
extension Stack: Sequence {
    public func makeIterator() -> ArrayIterator<T> {
        return ArrayIterator<T>(elements: self.elements)
    }
}

Swift运行时会调用makeIterator()方法来初始化for ... in循环。 我们将返回ArrayIterator的一个新实例,当我们的栈实例使用了后备数组时,该实例被初始化

Swift运行时会调用makeIterator()方法来初始化for…in循环。当Stack实例使用支持数组时,它将会返回一个新的已经被初始化了的ArrayIterator实例。

最后一个需要扩展的功能是添加另外一个构造器,它将允许我们从一个已经存在的栈中初始化一个新的Stack实例。需要注意的是,当我们创建一个副本时,需要调用数组的reversed()方法。这是因为Sequence.makeIterator()方法是由数组的构造器调用,并且从我们的栈中pop出一个元素。如果我们不对数组进行转置,最终我们将会从原始栈中创建出一个与存储序列相反的栈,而这并不是我们想要的。给Stack再添加一个构造器:

extension Stack: Sequence {
    public func makeIterator() -> ArrayIterator<T> {
        return ArrayIterator<T>(elements: self.elements)
    }
    
    // 再添加一个构造器,用于从一个已经存在的Stack实例中初始化出一个新的Stack实例
    public init<S : Sequence>(_ s: S) where S.Iterator.Element == T {
        self.elements = Array(s.reversed())
    }
}

// 快速声明
var aStack : Stack = [3, 5, 7, 9]

// 从已经存在的Stack实例中再初始化出一个新的实例
var bStack = Stack<Int>(aStack)

// 将元素44压入aStack中
aStack.push(element: 44)

// 将元素20压入bStack中
bStack.push(element: 20)

需要注意的是,当我们创建bStack实例变量时,它会从aStack中创建一个副本,并且当我们向bStack中添加元素时,aStack中的元素不会受影响。

关于Stack类型,我们仍然有改进的余地。我们可以利用Swift标准库现有的功能对迭代器和序列部分的代码进行优化。下面我们将会介绍三种新的类型,它们都是支持懒惰计算(lazy evaluation)的:

  • AnyIterator<Element>:它是一个抽象的IteratorProtocol基类型,与IteratorProtocol 类型有关,通常是当做序列类型来用;
  • AnySequence<Base>:创建一个与基类型具有相同元素的序列,当你调用.lazy时,会返回一个LazySequence类型;
  • IndexingIterator<Elements>:它是任意集合的迭代器,也是任意没有明确声明集合默认的迭代器。

接下来,我们就用上面这三种类型来改进与Sequence有关的扩展:

// 给Stack添加for...in支持
extension Stack: Sequence {
    
    // 改进Sequence协议的代码
    public func makeIterator() -> AnyIterator<T> {
        return AnyIterator(IndexingIterator(_elements:
            self.elements.lazy.reversed()))
    }
    
    // 再添加一个构造器
    public init<S : Sequence>(_ s: S) where S.Iterator.Element == T {
        self.elements = Array(s.reversed())
    }
}

上面的代码比较分散,看起来有些乱,为了方便阅读和查看,现在我们整理一下,把它们放在一起:

// 实现一个基本的Stack类型
public struct ArrayStack<T> {
    
    // 声明一个数组,用于存储栈里面的元素
    fileprivate var elements = [T]()
    
    // 构造器
    public init() {}
    
    // 构造器
    public init<S : Sequence>(_ s: S) where S.Iterator.Element == T {
        self.elements = Array(s.reversed())
    }
    
    // 出栈操作,并且将该元素从栈顶删除
    mutating public func pop() -> T? {
        
        /**
         *  在栈的实现过程中,我们是采用数组来存储元素的,而数组中有一个popLast()方法,
         *  这个方法在我们设计pop()方法,实现从栈顶删除元素时,可以提供有效的帮助
         */
        return self.elements.popLast()
        
    }
    
    // 入栈操作,将该元素添加到栈底
    mutating public func push(element: T){
        self.elements.append(element)
    }
    
    // 单纯的出栈操作,不会将元素从栈里删除
    public func peek() -> T? {
        return self.elements.last
    }
    
    // 判断栈是否为空
    public func isEmpty() -> Bool {
        return self.elements.isEmpty
    }
    
    // 返回栈中元素的个数
    public var count: Int {
        return self.elements.count
    }
    
}

// 用来实现数组字面意思声明语法
extension ArrayStack: ExpressibleByArrayLiteral {
    
    /**
     *  这个方法可以让你像下面这样定义一个栈:
     *  var myStack: ArrayStack<Int> = [4, 5, 6, 7]
     */
    public init(arrayLiteral elements: T...) {
        self.init(elements)
    }
}

// 当你打印类型时,可以输出好看的名称
extension ArrayStack: CustomStringConvertible, CustomDebugStringConvertible {
    
    /**
     *  在打印栈及其元素时,输出简介漂亮的格式。
     *  如果不实现这些代码,打印栈的结果为:ArrayStack<Int>(elements: [5, 44, 23])
     *  实现下面这些代码之后,打印栈的结果为:[5, 44, 23]
     */
    public var description: String {
        return self.elements.description
    }
    
    // 打印时输出简介的格式,主要用于调试
    public var debugDescription: String {
        return self.elements.debugDescription
    }
    
}

// 声明一个栈
var myStack = ArrayStack<Int>()

// 入栈操作
myStack.push(element: 5)
myStack.push(element: 44)
myStack.push(element: 23)

// 打印栈及其元素
print(myStack)

// 出栈操作(会从栈顶删除元素)
var x = myStack.pop()
x = myStack.pop()
x = myStack.pop()

// 前面的操作已经将栈里面的元素删除完了,所以这一步操作会返回nil
x = myStack.pop()

// 得到一个空的栈
x = myStack.pop()

// 打印栈
print(myStack)

// 继续进行入栈操作
myStack.push(element: 87)
myStack.push(element: 2)
myStack.push(element: 9)

// 打印栈
print(myStack)
print("**************")

// 使用新的元素覆盖原来栈里面的元素
myStack = [4,5,6,7]
print(myStack)

// 查看栈的类型
type(of: myStack)

print("**************")

/**
 *  给我们的栈添加for..in语法支持,并且实现从一个现有的栈中初始化出一个新
 *  的栈
 */
extension ArrayStack: Sequence {
    
    // 再添加一个构造器,用于从一个已经存在的Stack实例中初始化出一个新的Stack实例
    public func makeIterator() -> AnyIterator<T> {
        return AnyIterator(IndexingIterator(_elements: self.elements.lazy.reversed()))
    }
}

// 从现有的栈中初始化出一个新的栈
var stackFrommyStack = ArrayStack<Int>(myStack)

// 给新创建出来的栈添加元素
stackFrommyStack.push(element: 55)

// 继续给原来的栈添加元素
myStack.push(element: 70)
myStack.push(element: 10)

// 遍历原来栈中的元素
for el in myStack {
    print(el)
}

栈数据结构的基础知识,我们暂时先整理到这里,后面将会开始学习Swift队列(Queue)的相关知识。