《swift-algorithm-club》——数据结构/队列

·  阅读 336

队列(Queues)

栈可以保证元素存入和取出的顺序是后进先出(last-in first-out, LIFO)的。

public struct Stack<T> {
  fileprivate var array = [T]()
  
  public var isEmpty: Bool {
    return array.isEmpty
  }
  
  public var count: Int {
    return array.count
  }
  
  public mutating func push(_ element: T) {
    array.append(element)
  }
  
  public mutating func pop() -> T? {
    return array.popLast()
  }
  
  public var top: T? {
    return array.last
  }
}
复制代码

关于栈的有趣事情:每次调用函数或方法,CPU都会将函数返回地址压入运行栈中。当这个函数执行结束的时候,CPU将返回地址从栈中取出,并据此返回到函数被调用的位置。所以。如果不断地调用太多的函数(例如死递归函数),就会得到一个所谓的“栈溢出(stack overflow)”错误,因为CPU运行栈没有空间了。

队列

队列可以保证元素存入和取出的顺序是先进先出(first-in first-out, FIFO)的。

这是一个简单粗暴的队列实现。

public struct Queue<T> {
  fileprivate var array = [T][]
  
  public var isEmpty: Bool {
    return array.isEmpty
  }
  
  public var count: Int {
    return array.count
  }
  
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  public mutating func dequeue() -> T? {
    if isEmpty {
      return nil
    } else {
      return array.removeFirst()
    }
  }
  
  public var front: T? {
    return array.first
  }
}
复制代码

入队操作是O(1)的,出队操作是O(n)的。

更加高效的队列

public struct Queue<T> {
  fileprivate var array = [T?]()
  fileprivate var head = 0
  
  public var isEmpty: Bool {
    return count == 0
  }
  
  public var count: Int {
    return array.count - head
  }
  
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  public mutating func dequeue() -> T? {
    guard head < array.count, let element = array[head] else { return nil }
    
    array[head] = nil
    head += 1
    
    let percenttage = Double(head)/Double(array.count)
    if array.count > 50 && percentage > 0.25 {
      array.removeFirst(head)
      head = 0
    }
    return element
  }
  
  public var front: T? {
    if isEmpty {
      return nil
    } else {
      return array[head]
    }
  }
}
复制代码

在将队首元素出队时,我们首先将 array[head] 设置为 nil 来讲这个元素从数组中移除。然后将 head 的值加一,使得下一个元素变成新的队首值。

双端队列

一个基本实现

public struct Deque<T> {
  private var array = [T]()
  
  public var isEmpty: Bool {
    return array.isEmpty
  }
  
  public var count: Int {
    return array.count
  }
  
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  public mutating func enqueueFront(_ element: T) {
    array.insert(element, atIndex: 0)
  }
  
  public mutating func dequeue() -> T? {
    if isEmpty {
      return nil
    } else {
      return array.removeFrist()
    }
  }
  
  public mutating func dequeueBack() -> T? {
    if isEmpty {
      return nil
    } else {
      return array.removeLast()
    }
  }
  
  public func peekFront() -> T? {
    return array.first
  }
  
  public func peekBack() -> T? {
    return array.last
  }
}
复制代码

dequeue()enqueueFront() 的时间复杂度是O(n),因为它们在数组的前面工作。

更高效的版本

public struct Deque<T> {
  private var array: [T?]
  private var head: Int
  private var capacity: Int
  private let originalCapacity: Int
  
  public init(_ capacity: Int = 10) {
    self.capacity = max(capacity, 1)
    originalCapacity = self.capacity
    array = [T?](repeating: nil, count: capacity)
    head = capacity
  }
  
  public var isEmpty: Bool {
    return count == 0
  }
  
  public var count: Int {
    return array.count - head
  }
  
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  public mutating func enqueueFront(_ element: T) {
    if head == 0 {
      capacity *= 2
      let emptySpace = [T?](repeating: nil, count: capacity)
      array.insert(contentsOf: emptySpace, at: 0)
      head = capacity
    }
    
    head -= 1
    array[head] = element
  }
  
  public mutating func dequeue() -> T? {
    guard head < array.count, let element = array[head] else { return nil }
    
    array[head] = nil
    head += 1
    
    if capacity >= originalCapacity && head >= capacity*2 {
      let amountToRemove = capacity + capacity/2
      array.removeFirst(amountToRemove)
      head -= amountToRemove
      capacity /= 2
    }
    return element
  }
  
  public mutating func dequeueBack() -> T? {
    if isEmpty {
      return nil
    } else {
      return array.removeLast()
    }
  }
  
  public func peekFront() -> T? {
    if isEmpty {
      return nil
    } else {
      return array[head]
    }
  }
  
  public func peekBack() -> T? {
    if isEmpty {
      return nil
    } else {
      return array.last!
    }
  }
}
复制代码

优先级队列

优先级队列是一种队列,其中最重要的元素始终位于前面。

可以从优先级队列中受益的算法:

  • 事件驱动的模拟。每个事件都有一个时间戳,您希望按照时间戳的顺序执行事件。优先级队列可以轻松找到需要模拟的下一个事件。
  • Dijkstra 的图搜索算法使用优先级队列来计算最低成本。
  • 霍夫曼编码用于数据压缩。该算法构建压缩树。它反复需要找到具有最小频率且尚未具有父节点的两个节点。
  • 用于人工智能的A*寻路。
  • 很多其他地方!

基于堆的Swift优先级队列:

public struct PriorityQueue<T> {
  fileprivate var heap: Heap<T>
  
  public init(sort: (T, T) -> Bool) {
    heap = Heap(sort: sort)
  }
  
  public var isEmpty: Bool {
    return heap.isEmpty
  }
  
  public var count: Int {
    return heap.count
  }
  
  public var peek() -> T? {
    return heap.peek()
  }
  
  public mutating func enqueue(element: T) {
    heap.insert(element)
  }
  
  public mutating func dequeue() -> T? {
    return heap.remove()
  }
  
  public mutating func changePriority(index i: Int, value: T) {
    return heap.replace(index: i, value: value)
  }
}
复制代码

环形缓冲区

也被称为循环缓冲区。

这是一个概念性地回绕到开头的数组,因此您永远不必删除任何项目。所有操作都是O(1)。

Swift的一个简单实现

public struct RingBuffer<T> {
  fileprivate var array: [T?]
  fileprivate var readIndex = 0
  fileprivate var writeIndex = 0
  
  public init(count: Int) {
    array = [T?](repeating: nil, count: count)
  }
  
  public mutating func write(_ element: T) -> Bool {
    if !isFull {
      array[writeIndex % array.count] = element
      writeIndex += 1
      return true
    } else {
      return false
    }
  }
  
  public mutating func read() -> T? {
    if !isEmpty {
      let element = array[readIndex % array.count]
      readIndex += 1
      return element
    } else {
      return nil
    }
  }
  
  fileprivate var availableSpaceForReading: Int {
    return writeIndex - readIndex
  }
  
  public var isEmpty: Bool {
    return availableSpaceForReading == 0
  }
  
  fileprivate var availableSpaceForWriting: Int {
    return array.count - availableSpaceForReading
  }
  
  public var isFull: Bool {
    return availableSpaceForWriting == 0 
  }
}
复制代码

RingBuffer对象有一个数组用于实际存储数据,readIndexwriteIndex 变量用于指向数组的“指针”。wirte() 函数将新元素放入writeIndex 中的数组中,read() 函数返回 readIndex中的元素。

使用的时候是这样的:

var buffer = RingBuffer<Int>(count: 5)

buffer.write(123)
buffer.write(456)
buffer.write(789)
buffer.write(666)

buffer.read()    // 123
buffer.read()    // 456
buffer.read()    // 789

buffer.write(333)
buffer.wirte(555)

buffer.read()    // 666
buffer.read()    // 333
buffer.read()    // 555
buffer.read()    // nil
复制代码

环形缓冲区可以创建更优的队列,但它也有一个缺点:包装使得调整队列大小变得棘手。不过如果固定大小的队列适合你的目的,那就非常合适了。

当数据生产者以不同于数据使用者读取数据的速率写入数组时,环形缓冲区也非常有用。这通常发生在文件或网络I/O上。环形缓冲区也是高优先级线程 (例如音频渲染回调) 与系统其他较慢部分之间通信的首选方式。

分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改