隊列(Queue)是一種先入先出(First in First Out,簡稱FIFO)的數(shù)據(jù)結構。想象一下,在排隊買飯的時候,站在隊列最前邊的人買完飯之后出去了,然后排在他后面的人接著上前買飯,直到最后一個人也買完飯走了,這是隊列最形象的一個例子(當然,這里不考慮插隊這種敗人品的bug)。我們來看一下隊列結構示意圖:
根據(jù)隊列的特點,通常情況下,我們需要實現(xiàn)下面這些屬性和方法:
- enqueue():將元素添加到隊列的末尾;
- dequeue():刪除隊列中第一個元素,并且將其返回;
- peek():返回隊列中第一個元素,但是不會將其從隊列中刪除;
- clear():清空隊列中的元素;
- count:返回隊列中元素的個數(shù);
- isEmpty():判斷隊列是否為空,如果是,則返回true,否則返回false;
- isFull():判斷隊列是否已滿,如果是,則返回true,否則返回false。
- capacity:用來查看或者設置隊列容量的屬性。
補充一點,數(shù)組的count屬性和capacity屬性是有區(qū)別的。count屬性用來返回數(shù)組中元素的個數(shù),而capacity屬性是用來返回數(shù)組的存儲空間的。
1、隊列的應用
隊列的應用場景也非常的多,比如說你需要按照接收順序來處理相關的數(shù)據(jù)。舉一個典型的例子,為了解決計算機和打印機之間速度不匹配的問題,通常需要設置一個打印數(shù)據(jù)的緩沖區(qū),主機將要輸出的數(shù)據(jù)依次寫入該緩沖區(qū),而打印機則依次從該緩沖區(qū)中取出數(shù)據(jù),此時緩沖區(qū)的邏輯實現(xiàn)就要用到隊列。
2、隊列的實現(xiàn)
和上一篇文章Stack的實現(xiàn)一樣,在實現(xiàn)隊列時,我們依然要用到數(shù)組和泛型語法。數(shù)組用來存儲數(shù)據(jù)元素,而泛型語法在具體的實現(xiàn)時提供必要的靈活性。接下來,我們要依次實現(xiàn)enqueue()、dequeue()、peek()、isEmpty()、isFull()、clear()方法及count屬性:
// 定義一個隊列結構
public struct Queue<T> {
// 數(shù)組用來存儲數(shù)據(jù)元素
fileprivate var data = [T]()
// 構造方法,用于構建一個空的隊列
public init() {}
// 構造方法,用于從序列中創(chuàng)建隊列
public init<S: Sequence>(_ elements: S) where
S.Iterator.Element == T {
data.append(contentsOf: elements)
}
// 將類型為T的數(shù)據(jù)元素添加到隊列的末尾
public mutating func enqueue(element: T) {
data.append(element)
}
// 移除并返回隊列中第一個元素
// 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
public mutating func dequeue() -> T? {
return data.removeFirst()
}
// 返回隊列中的第一個元素,但是這個元素不會從隊列中刪除
// 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
public func peek() -> T? {
return data.first
}
// 清空隊列中的數(shù)據(jù)元素
public mutating func clear() {
data.removeAll()
}
// 返回隊列中數(shù)據(jù)元素的個數(shù)
public var count: Int {
return data.count
}
// 返回或者設置隊列的存儲空間
public var capacity: Int {
get {
return data.capacity
}
set {
data.reserveCapacity(newValue)
}
}
// 檢查隊列是否已滿
// 如果隊列已滿,則返回true;否則,返回false
public func isFull() -> Bool {
return count == data.capacity
}
// 檢查隊列是否為空
// 如果隊列為空,則返回true;否則,返回false
public func isEmpty() -> Bool {
return data.isEmpty
}
}
上面的代碼定義了一個基本的隊列,下面來演示一下將數(shù)據(jù)元素添加到隊列,以及從隊列中刪除數(shù)據(jù)元素的基本操作:
// 聲明一個存儲String類型數(shù)據(jù)的隊列
var queue = Queue<String>()
// 將風塵四俠添加到隊列中
queue.enqueue(element: "LeBron James")
queue.enqueue(element: "Carmelo Anthony")
queue.enqueue(element: "Dwyane Wade")
queue.enqueue(element: "Chris Paul")
// 從隊列中取出LeBron James,并將其刪除
let x = queue.dequeue()
// 從隊列中取出Carmelo Anthony,但是不要從隊列中刪除
let y = queue.peek()
// 刪除隊列中的第一個元素(此時返回的應該是Carmelo Anthony)
let z = queue.dequeue()
關于上面隊列的代碼,其功能還是相當簡單的,我們只是將一個數(shù)組包裝成了一個隊列,但是距離真正的隊列還有一段距離。另外需要強調一點,此時隊列的存儲容量是由數(shù)組自己動態(tài)調整來實現(xiàn)的,我們并沒有對其進行干預。
3、通過協(xié)議來擴展隊列的功能
要想讓在上面定義的Queue類型看起來更像一個隊列,我們還需要利用一些便利協(xié)議來擴展其功能。首先,我們先給它擴展CustomStringConvertible協(xié)議和CustomDebugStringConvertible協(xié)議,這會讓我們在打印隊列時,控制臺會輸出簡介漂亮的格式:
// 讓打印隊列時輸出簡介的格式
extension Queue: CustomStringConvertible, CustomDebugStringConvertible {
// 控制打印隊列時的文本輸出
public var description: String {
return data.description
}
// 控制打印隊列時的文本輸出,主要用于調試
public var debugDescription: String {
return data.debugDescription
}
}
如果我們沒有實現(xiàn)上面的代碼,在打印隊列時,控制臺會顯示下面的字符串:
Queue<String>(data: ["LeBron James", "Carmelo Anthony", "Dwyane Wade", "Chris Paul"])
但是,當我們實現(xiàn)了上面的擴展,再打印隊列時,控制臺會顯示下面這樣的字符串:
["LeBron James", "Carmelo Anthony", "Dwyane Wade", "Chris Paul"]
從對比中我們可以看到,實現(xiàn)上面的擴展之后,控制臺打印出來的字符串看起來更加的友好。
接著,我們要讓隊列可以使用字面語法(也就是快速聲明語法)來初始化一個實例。為此,必須讓隊列遵守ExpressibleByArrayLiteral協(xié)議。要實現(xiàn)這個功能,還要給Queue增加一個新的構造器,該構造器可以初始化出一個序列,讓序列中包含所有初始化的元素:
/// 構造方法,用于從序列中創(chuàng)建隊列(添加到Queue的類型定義中)
public init<S: Sequence>(_ elements: S) where
S.Iterator.Element == T {
data.append(contentsOf: elements)
}
// 讓隊列支持通過快速聲明來創(chuàng)建實例
extension Queue: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: T...) {
self.init(elements)
}
}
// 使用快速語法聲明一個隊列
var myQueue : Queue = [5, 8, 1, 6, 9, 11]
print(myQueue)
除了按照字面意思快速聲明一個隊列之外,我們可能還希望像集合類型一樣,可以在隊列中使用for...in循環(huán)語句。為此,必須讓我們定義的Queue遵守Sequence協(xié)議,這樣它就可以返回一個懶加載的序列:
// 擴展隊列的for...in循環(huán)功能
extension Queue: Sequence {
// 從序列中返回一個迭代器
public func generate() -> AnyIterator<T> {
return AnyIterator(IndexingIterator(_elements: data.lazy))
}
}
寫完上面的代碼之后,Playground可能會報Type 'Queue<T>' does not conform to protocol 'Sequence'的錯誤,主要原因是我們在上面用到了IndexingIterator。IndexingIterator定義在Collection協(xié)議中,并且是繼承自_IndexableBase協(xié)議。而_IndexableBase協(xié)議在Swift 4中將會被移除,所以我們不用鳥它:
為了實現(xiàn)for...in循環(huán)的功能,同時解決編譯器報錯,我們需要遵守Collection協(xié)議。除此之外,集合類型中另外一個非常有用的協(xié)議是MutableCollection,它允許你使用下標來檢索和設置隊列中的元素。通過下標的使用,我們可以指定隊列中元素的索引。不過,在使用索引之前,我們還需要對它的合法性進行驗證,為此需要實現(xiàn)一個checkIndex()方法:
// 現(xiàn)在Queue的定義中添加一個方法,用于檢查索引的合法性
fileprivate func checkIndex(index: Int) {
if index < 0 || index > count {
fatalError("Index out of range")
}
}
/** 接下來是對Queue進行擴展 **/
// 根據(jù)索引返回指定的位置
extension Queue: Collection {
// i的值必須比endIndex小
public func index(after i: Int) -> Int {
// 返回i后面的索引
return data.index(after: i)
}
}
// 實現(xiàn)下標功能
extension Queue: MutableCollection {
// 隊列的起始索引
public var startIndex: Int {
return 0
}
// 隊列末尾索引
public var endIndex: Int {
return count - 1
}
// 獲取或者設置下標
public subscript(index: Int) -> T {
get {
checkIndex(index: index)
return data[index]
}
set {
checkIndex(index: index)
data[index] = newValue
}
}
}
實現(xiàn)完上面的代碼之后,我們不僅可以使用for...in循環(huán)來遍歷隊列,還可以獲取隊列中指定位置的索引:
// 遍歷隊列中的元素
for el in myQueue {
print(el)
}
// 獲取隊列中指定下標后面的索引
myQueue.index(after: 0)
// 獲取隊列中第一個元素的索引
myQueue.startIndex
// 獲取隊列中最后一個元素的索引
myQueue.endIndex
最后,為了便于閱讀和理解,我們還是按照習慣,將所有的代碼整理到一起:
// 定義一個隊列結構
public struct Queue<T> {
// 數(shù)組用來存儲數(shù)據(jù)元素
fileprivate var data = [T]()
// 構造方法,用于構建一個空的隊列
public init() {}
// 構造方法,用于從序列中創(chuàng)建隊列
public init<S: Sequence>(_ elements: S) where
S.Iterator.Element == T {
data.append(contentsOf: elements)
}
// 將類型為T的數(shù)據(jù)元素添加到隊列的末尾
public mutating func enqueue(element: T) {
data.append(element)
}
// 移除并返回隊列中第一個元素
// 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
public mutating func dequeue() -> T? {
return data.removeFirst()
}
// 返回隊列中的第一個元素,但是這個元素不會從隊列中刪除
// 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
public func peek() -> T? {
return data.first
}
// 清空隊列中的數(shù)據(jù)元素
public mutating func clear() {
data.removeAll()
}
// 返回隊列中數(shù)據(jù)元素的個數(shù)
public var count: Int {
return data.count
}
// 返回或者設置隊列的存儲空間
public var capacity: Int {
get {
return data.capacity
}
set {
data.reserveCapacity(newValue)
}
}
// 檢查隊列是否已滿
// 如果隊列已滿,則返回true;否則,返回false
public func isFull() -> Bool {
return count == data.capacity
}
// 檢查隊列是否為空
// 如果隊列為空,則返回true;否則,返回false
public func isEmpty() -> Bool {
return data.isEmpty
}
// 確保隊列中的索引是合法的
fileprivate func checkIndex(index: Int) {
if index < 0 || index > count {
fatalError("Index out of range")
}
}
}
/**************** 隊列的基本操作 ****************/
// 聲明一個存儲String類型數(shù)據(jù)的隊列
var queue = Queue<String>()
// 將風塵四俠添加到隊列中
queue.enqueue(element: "LeBron James")
queue.enqueue(element: "Carmelo Anthony")
queue.enqueue(element: "Dwyane Wade")
queue.enqueue(element: "Chris Paul")
// 打印隊列
print(queue)
// 從隊列中取出LeBron James,并將其刪除
let x = queue.dequeue()
// 從隊列中取出Carmelo Anthony,但是不要從隊列中刪除
let y = queue.peek()
// 刪除隊列中的第一個元素(此時返回的應該是Carmelo Anthony)
let z = queue.dequeue()
/**************** 利用協(xié)議來擴展隊列的功能 ****************/
// 讓打印隊列時輸出簡介的格式
extension Queue: CustomStringConvertible, CustomDebugStringConvertible {
// 控制打印隊列時的文本輸出
public var description: String {
return data.description
}
// 控制打印隊列時的文本輸出,主要用于調試
public var debugDescription: String {
return data.debugDescription
}
}
// 讓隊列支持通過快速聲明來創(chuàng)建實例
extension Queue: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: T...) {
self.init(elements)
}
}
// 使用快速語法聲明一個隊列
var myQueue : Queue = [5, 8, 1, 6, 9, 11]
print(myQueue)
// 擴展隊列的for...in循環(huán)功能
extension Queue: Sequence {
// 從序列中返回一個迭代器
public func generate() -> AnyIterator<T> {
return AnyIterator(IndexingIterator(_elements: data.lazy))
}
}
// 根據(jù)索引返回指定的位置
extension Queue: Collection {
// i的值必須比endIndex小
public func index(after i: Int) -> Int {
// 返回i后面的索引
return data.index(after: i)
}
}
// 實現(xiàn)下標功能
extension Queue: MutableCollection {
// 隊列的起始索引
public var startIndex: Int {
return 0
}
// 隊列末尾索引
public var endIndex: Int {
return count - 1
}
// 獲取或者設置下標
public subscript(index: Int) -> T {
get {
checkIndex(index: index)
return data[index]
}
set {
checkIndex(index: index)
data[index] = newValue
}
}
}
// 編譯器有bug,遍歷顯示有錯,但是仍然可以成功遍歷
for el in myQueue {
print(el)
}
// 獲取隊列中指定下標后面的索引
myQueue.index(after: 0)
// 獲取隊列中第一個元素的索引
myQueue.startIndex
// 獲取隊列中最后一個元素的索引
myQueue.endIndex
好了,Swift隊列數(shù)據(jù)結構的基本實現(xiàn)就先整理到這里,在下一篇中,我們將會繼續(xù)學習環(huán)形緩沖區(qū)的相關知識。