Swift算法俱樂部:Swift 鏈表數據結構@(Swift)在本教程中,我們將會在Swift 3中實現鏈表。##Getting Started鏈表是一個序列化的數據項,每一個項目都作為節點(node)被引用。有兩個主要類型的鏈表:單向鏈表,每個節點有一個指向到下一個節點的引用。
Alt text
雙向鏈表,每個節點各有一個向前和向后的節點的引用。
Alt text
我們還需要去跟蹤鏈表的開頭和結尾,通常管這兩個指針叫做head和tail。
Alt text
##Swift 3中實現鏈表這部分,我們將會在Swift 3中實現鏈表。記?。烘湵硎怯晒濣c組成的。所以需要先創建基本節點類。創建一個全新的Swift Playground并且添加一個Class:swiftpublic class Node {}
###值節點需要關聯一個值,在Node類的中括號中添加下面的代碼:swiftvar value: String init(value: String) { self.value = value}
聲明了一個屬性,它的名字叫做value,是String類型。在我們的app中這可能是你想存儲的類型。還聲明了一個初始化方法,需要初始化所有非可選的屬性。###Next另外,還需要一個值,每個節點都需要一個指針指向到鏈表中的下一個節點。在類中添加另外一個屬性:swiftvar next: Node?
這里聲明了一個next屬性,它是Node類型。注意next屬性是可選,因為在鏈表中的最后一個節點不會指向任何的節點。###Previous我們將會實現一個雙向鏈表,所以我們還需要一個指向之前節點的指針。在類中添加另外一個屬性:swiftweak var previous: Node?
>注意:為了避免所有權的循環引用,聲明的previous指針是弱引用。如果你有一個節點A,它的下一個節點是B,這樣A指向B,B也指向A。在某些的情況下,這樣會導致節點即使在被刪除以后還會存在的情況。這是我們所不希望的,所以需要讓其中的一個指針為弱引用來避免出現這樣的問題。###鏈表現在已經創建的節點還需要確定它的起點和終點。添加新的LinkedList類到playground的底部:swiftpublic class LinkedList { fileprivate var head: Node? private var tail: Node? public var isEmpty: Bool { return head == nil } public var first: Node? { return head } public var last: Node? { return tail }}
這個類將會跟蹤鏈表的開頭和結尾,同樣也會提供幾個有用的方法。###Append處理添加一個新的節點到鏈表,我們將會在LinkedList類中聲明一個append(value:)方法。swiftpublic func append(value: String) { // 1 let newNode = Node(value: value) // 2 if let tailNode = tail { newNode.previous = tailNode tailNode.next = newNode } // 3 else { head = newNode } // 4 tail = newNode}
回顧這部分的代碼:創建一個包含值的節點。記住,創建Node類的目的是讓每一個項目都可以指向到之前或之后的節點。如果tailNode不為空,就意味著鏈表中有些東西,配置這個新的項目的previous指向到鏈表的tailNode,并且配置tailNode的next指向到新的項目。最后,設置鏈表的tail為最新創建的項目對象。###打印鏈表讓我們試著輸出鏈表。在LinkedList類的外面:swiftlet dogBreeds = LinkedList()dogBreeds.append(value: "Labrador")dogBreeds.append(value: "Bulldog")dogBreeds.append(value: "Beagle")dogBreeds.append(value: "Husky")
在定義鏈表的后面,嘗試打印:swiftprint(dogBreeds)
你可以通過組合鍵:Command-Shift-Y啟動調式控制臺,可以看到下面的輸出:LinkedList
這種輸出沒有任何的價值,要想顯示更多的有用的文字信息,可以讓LinkedList類采用CustomStringConvertable協議。在LinkedList類中實現下面的代碼:swift// 1extension LinkedList: CustomStringConvertible { // 2 public var description: String { // 3 var text = "[" var node = head // 4 while node != nil { text += "\(node!.value)" node = node!.next if node != nil { text += ", " } } // 5 return text + "]" }}
上面代碼的意思是:1. 聲明了LinkedList類的擴展,并且采用CustomStringConvertible協議。這個協議希望你實現一個計算屬性description,該屬性是String類型。2. 聲明的description屬性是計算屬性,它是一個只讀的并且返回字符串的屬性。3. 聲明的text變量,它會生成整個的字符串?,F在它包含一個左中括號,然后是鏈表的開始。4. 通過循環鏈表,將鏈表中項目的值依次添加到text中。5. 添加一個右中括號到text變量?,F在,當你調用LinkedList類的print方法,將會看到一個非常美妙的鏈表描述:"[Labrador, Bulldog, Beagle, Husky]"
###訪問節點盡管通過鏈表的next和previous方法可以很有效的去按順序獲取每個節點,但有的時候通過索引的方式對于我們來說可能會更加的簡單。我們會在LinkedList類中聲明一個nodeAt(index: )方法,它將返回一個指定索引的節點。 修改LinkedList中的代碼:swiftpublic func nodeAt(index: Int) -> Node? { // 1 if index >= 0 { var node = head var i = index // 2 while node != nil { if i == 0 { return node } i -= 1 node = node!.next } } // 3 return nil}
上面的代碼是這樣的:1. 添加對指定索引值的檢測,看它是否為非負數。這樣可以防止負數出現的無限循環。2. 循環節點,直到到達了那個指定索引值的節點,就會返回這個node。3. 如果索引值小于0,或者是大于鏈表的項目數,則會返回nil。###移除所有節點移除所有節點,我們只需要讓head和tail為nil即可。swiftpublic func removeAll() { head = nil tail = nil}
###移除單個節點移除單個節點,我們需要考慮下面的三種情況:1. 移除第一個節點,需要修改head和previous指針:
Alt text
2. 移除中間的節點,需要修改previous和next:
Alt text
3. 移除最后節點,需要修改next和tail指針:
Alt text
修改LinkedList類:swiftpublic func remove(node: Node) -> String { let prev = node.previous let next = node.next if let prev = prev { prev.next = next // 1 } else { head = next // 2 } next?.previous = prev // 3 if next == nil { tail = prev // 4 } // 5 node.previous = nil node.next = nil // 6 return node.value}
1. 如果移除的節點不是第一節點則修改next指針。2. 如果移除的節點是第一節點則修改head。3. 修改刪除節點的下一個節點的previous。4. 如果刪除的是最后一個節點,修改tail。5. 給刪除節點的previous和next賦值nil。6. 返回移除節點的值。##Generics之前,我們已經實現了可以存儲字符串的基本鏈表類。已經提供了添加、移除和訪問節點的功能。這部分,我們將會使用范式抽象出所有的類型鏈表。修改之前的Node類:swift// 1public class Node<T> { // 2 var value: T var next: Node<T>? weak var previous: Node<T>? // 3 init(value: T) { self.value = value }}
1. 改變Node類的聲明為范式類型T。2. 我們的目的是允許Node類存儲任何的類型,所以構建value的類型也為T,而不是之前的String。3. 修改初始化方法。修改LinkedList類使用范式。swift// 1. Change the declaration of the Node class to take a generic type Tpublic class LinkedList<T> { // 2. Change the head and tail variables to be constrained to type T fileprivate var head: Node<T>? private var tail: Node<T>? public var isEmpty: Bool { return head == nil } // 3. Change the return type to be a node constrained to type T public var first: Node<T>? { return head } // 4. Change the return type to be a node constrained to type T public var last: Node<T>? { return tail } // 5. Update the append function to take in a value of type T public func append(value: T) { let newNode = Node(value: value) if let tailNode = tail { newNode.previous = tailNode tailNode.next = newNode } else { head = newNode } tail = newNode } // 6. Update the nodeAt function to return a node constrained to type T public func nodeAt(index: Int) -> Node<T>? { if index >= 0 { var node = head var i = index while node != nil { if i == 0 { return node } i -= 1 node = node!.next } } return nil } public func removeAll() { head = nil tail = nil } // 7. Update the parameter of the remove function to take a node of type T. Update the return value to type T. public func remove(node: Node<T>) -> T { let prev = node.previous let next = node.next if let prev = prev { prev.next = next } else { head = next } next?.previous = prev if next == nil { tail = prev } node.previous = nil node.next = nil return node.value }}
所有代碼已經完成,可以實地測試了:swiftlet dogBreeds = LinkedList<String>()dogBreeds.append(value: "Labrador")dogBreeds.append(value: "Bulldog")dogBreeds.append(value: "Beagle")dogBreeds.append(value: "Husky") let numbers = LinkedList<Int>()numbers.append(value: 5)numbers.append(value: 10)numbers.append(value: 15)

Alt text

Alt text

Alt text

Alt text

Alt text

Alt text