從本文標題中的序號能夠看出,本文是一個連載的開篇。html
並且這個連載的標題是:數據結構 & 算法 in Swift。從這個連載的標題中能夠看出,筆者分享的是使用Swift語言來實現所學的的數據結構和算法的知識。這裏面須要解釋兩點:node
學習通用性知識,突破技能瓶頸:筆者作iOS開發也有兩年了,這期間經過從項目,第三方源碼,相關應用類的編程書籍提升了些技術水平。而做爲沒學過數據結構和算法的非科班大軍中的一員,這些知識始終是繞不過去的。由於對此類知識的掌握程度會對從此編程技能的提升有着無可估量的影響,因此就決定學習了。git
選擇哪一個語言並不重要,重要的是數據結構和算法自己的理解:經過兩個星期的學習,現在筆者已經可使用Swift語言來實現幾種數據結構和算法了,但我相信若是我使用C語言或者Objective-C語言的話會學得更快些,由於在實現的時候因爲對該語言的不熟悉致使在實現過程當中踩了很多坑。不過能夠反過來思考:若是我可使用Swift來實現這些,那麼我從此用C,Objective-C,甚至是Java就容易多了,再加上我還順便學習了Swift不是麼?程序員
現在Swift的勢頭還在上漲:筆者已經觀察到不少新的庫,教學都使用了Swift語言。並且據說一些面試的朋友在面試過程當中多少有問過Swift相關的知識,一些公司的新項目也有用Swift寫了。github
基於上面這些緣由,在今年年初把數據結構,算法和Swift的學習提上了日程,而且計劃以連載的形式把學習過程當中的筆記和知識分享出來。面試
該系列的最佳受衆是那些已經會Swift,可是對數據結構和算法尚未過多接觸過的iOS開發者。其次是那些不會Swift也不會數據結構和算法的iOS開發者,畢竟Swift是大勢所趨。算法
不過對於那些非iOS開發者來講也一樣適合,由於仍是那句話:重點不在於使用哪一種語言,而是數據結構和算法自己。除了第一篇會講解一些在這個系列文章會使用到的Swift基礎語法之外,後續的文章我會逐漸弱化對Swift語言的講解,將重點放在數據結構和算法這裏。並且後續我還會不斷增長其餘語言的實現(Java語言是確定要加的,其餘的語言還待定)。編程
好了,背景介紹完了,如今正式開始:swift
做爲該系列的開篇,本文分爲兩個部分:數組
注:該系列涉及到的Swift語法最低基於Swift4.0。
Swift語法基礎從如下幾點來展開:
Swift將循環的開閉區間作了語法上的簡化:
for index in 1...5 {
print("index: \(index)")
}
// index : 1
// index : 2
// index : 3
// index : 4
// index : 5
複製代碼
for index in 1..<5 {
print("index: \(index)")
}
// index : 1
// index : 2
// index : 3
// index : 4
複製代碼
上面兩個例子都是升序的(index從小到大),咱們來看一降低序的寫法:
for index in (1..<5).reversed() {
print("index: \(index)")
}
// index : 4
// index : 3
// index : 2
// index : 1
複製代碼
降序的應用能夠在下篇的冒泡排序算法中能夠看到。
使用泛型能夠定義一些可複用的函數或類型,Swift中的Array和Dictionary都是泛型的集合。
爲了體現出泛型的意義,下面舉一個例子來講明一下:
實現這樣一個功能:將傳入該函數的兩個參數互換。
整型的交換:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
複製代碼
字符串的交換:
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let tmp = a
a = b
b = tmp
}
複製代碼
浮點型的交換:
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let tmp = a
a = b
b = tmp
}
複製代碼
上面這三種狀況的實現部分其實都是同樣的,但僅僅是由於傳入類型的不一致,致使對於不一樣的類型還要定義一個新的函數。因此若是類型有不少的話,定義的新函數也會不少,這樣顯然是不夠優雅的。
此類問題可使用泛型來解決:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
複製代碼
上面函數中的T是泛型的固定寫法,能夠理解爲「全部類型」。這樣一來,咱們能夠傳入任何相同的類型來做交換了。
泛型還有其餘比較強大的功能,因爲在後續的數據結構和算法的講解裏面可能不會涉及到,因此在這裏先不贅述了。有興趣的朋友能夠參考官方文檔:Swift:Generics
guard是 swift 2.0推出的新的判斷語句的用法。
與if語句相同的是,guard也是基於一個表達式的布爾值去判斷一段代碼是否該被執行。與if語句不一樣的是,guard只有在條件不知足的時候纔會執行這段代碼。你能夠把guard近似的看作是Assert,可是你能夠優雅的退出而非崩潰
使用guard語法,能夠先對每一個條件逐一作檢查,若是不符合條件判斷就退出(或者進行其餘某些操做)。這就會讓人容易看出來什麼條件會讓這個函數退出(或者進行其餘某些操做)。
能夠用一個例子來分別使用if和guard來實現,體會兩者的區別:
//money: holding moneny (用戶持有的錢數)
//price: product price (商品的價格)
//capacity: bag capacity (用戶用來裝商品的袋子容量)
//volume: product size (商品的大小)
func buying1( money: Int , price: Int , capacity: Int , volume: Int){
if money >= price{
if capacity >= volume{
print("Start buying...")
print("\(money-price) money left after buying.")
print("\(capacity-volume) capacity left after buying.")
}else{
print("No enough capacity")
}
}else{
print("No enough money")
}
}
複製代碼
從上面的邏輯能夠看出,當同時知足:
這兩個狀況的時候,購買纔會進行,其餘全部狀況都沒法引起購買。
對於大多數習慣使用if-else的朋友來講,上面的代碼理解來並無難度,可是相同的邏輯,咱們看一下使用guard以後的效果:
func buying2( money: Int , price: Int , capacity: Int , volume: Int){
guard money >= price else{
print("No enough money")
return
}
guard capacity >= volume else{
print("No enough capacity")
return
}
print("Start buying...")
print("\(money-price) money after buying.")
print("\(capacity-volume) capacity left after buying.")
}
複製代碼
從上面的實現能夠看出:
money < price
和capacity < volume
這兩個狀況首先排除掉並填上了相應的處理代碼。所以經過兩個guard判斷的語句,咱們知道該函數所處理的正確邏輯是什麼,很是清晰。
由於後續的數據結構和算法的講解是離不開函數的使用的,因此在這裏簡單介紹一下Swift中函數的使用。
func log(message: String) {
print("log: \(message)!")
}
log(message: "memory warning")
// output: log: memory warning!
複製代碼
func logString(string: String) -> String {
return "log: " + string
}
let logStr = logString(string: "memory warning!")
print("\(logStr)")
// output: log: memory warning!
複製代碼
經過在函數形參前面加上_
,能夠起到在調用時省略外部參數的做用:
func logMessage(_ message: String) {
print("log: \(message)!")
}
logMessage("memory warning")
// output: log: memory warning!
複製代碼
再來看一下兩個參數的狀況:
func addInt(_ a : Int ,_ b : Int){
print("sum is \(a + b)")
}
addInt(3, 4)
//output : sum is 7
複製代碼
Swift中,struct是按值傳遞,class是按引用傳遞。數組和字典在Swift裏是屬於struct,因此須要若是在一個函數裏要修改傳入的數組,須要作特殊處理:
var originalArr = [2,1,3]
func removeLastInArray(_ array: inout [Int]){
array.removeLast()
}
print("\n============ before removing: \(originalArr)")
//[2, 1, 3]
removeLastInArray(&originalArr)
print("============ after removing: \(originalArr)")
//[2, 1]
複製代碼
在這裏使用的inout
關鍵字就是將傳入的數組改成引用傳遞了。
Swift裏的集合類型有:數組,集合,字典,下面來分別講一下。
這三種類型都支持泛型,也就是說裏面的元素能夠是整數,字符串,浮點等等。
Swift’s
Array
type is bridged to Foundation’sNSArray
class.
// immutable array
let immutableNumbers: [Int] = [1, 3, 5, 4, 4, 1]
// mutable array
var mutableNumbers : [Int] = [2, 1, 5, 4, 1, 3]
複製代碼
Swift中能夠用
let
和var
來分別聲明可變和不可變數組:數組的添加刪除等操做只能做用於可變數組。
// iteration 1
for value in mutableNumbers {
if let index = mutableNumbers.index(of: value) {
print("Index of \(value) is \(index)")
}
}
// iteration 2
mutableNumbers.forEach { value in
if let index = mutableNumbers.index(of: value) {
print("Index of \(value) is \(index)")
}
}
// iteration 3
for (index, value) in mutableNumbers.enumerated() {
print("Item \(index + 1): \(value)")
}
複製代碼
mutableNumbers.append(11)
// Output: [2, 1, 5, 4, 1, 3, 11]
mutableNumbers.insert(42, at: 4)
// Output: [2, 1, 5, 4, 42, 1, 3, 11]
mutableNumbers.swapAt(0, 1)
// Output: [1, 2, 5, 4, 42, 1, 3, 11]
mutableNumbers.remove(at: 1)
// Output: [2, 5, 4, 42, 1, 3, 11]
mutableNumbers.removeFirst()
// Output: [5, 4, 42, 1, 3, 11]
mutableNumbers.removeLast()
// Output: [5, 4, 42, 1, 3]
mutableNumbers.removeAll()
//[]
複製代碼
append函數的做用是在數組的末尾添加元素
swapAt函數的做用是交換在傳入的兩個index上的元素,該方法在下篇的排序算法中使用得很是頻繁。
Swift’s
Set
type is bridged to Foundation’sNSSet
class.
關於集合與數組的區別,除了數組有序,集合無序之外,數組內部的元素的數值能夠不是惟一的;可是集合裏元素的數值必須是惟一的,若是有重複的數值會算做是一個:
//value in set is unique
let onesSet: Set = [1, 1, 1, 1]
print(onesSet)
// Output: [1]
let onesArray: Array = [1, 1, 1, 1]
print(onesArray)
// Output: [1, 1, 1, 1]
複製代碼
let numbersSet: Set = [1, 2, 3, 4, 5]
print(numbersSet)
// Output: undefined order, e.g. [5, 2, 3, 1, 4]
// iteration 1
for value in numbersSet {
print(value)
}
// output is in undefined order
// iteration 2
numbersSet.forEach { value in
print(value)
}
// output is in undefined order
複製代碼
var mutableStringSet: Set = ["One", "Two", "Three"]
let item = "Two"
//contains
if mutableStringSet.contains(item) {
print("\(item) found in the set")
} else {
print("\(item) not found in the set")
}
//isEmpty
let strings = Set<String>()
if strings.isEmpty {
print("Set is empty")
}
//count
let emptyStrings = Set<String>()
if emptyStrings.count == 0 {
print("Set has no elements")
}
//insert
mutableStringSet.insert("Four")
//remove 1
mutableStringSet.remove("Three")
//remove 2
if let removedElement = mutableStringSet.remove("Six") {
print("\(removedElement) was removed from the Set")
} else {
print("Six is not found in the Set")
}
//removeAll()
mutableStringSet.removeAll()
// []
複製代碼
A dictionary
Key
type must conform to theHashable
protocol, like a set’s value type.
//empty dictionary
var dayOfWeek = Dictionary<Int, String>()
var dayOfWeek2 = [Int: String]()
//not empty dictionary
var dayOfWeek3: [Int: String] = [0: "Sun", 1: "Mon", 2: "Tue"]
print(dayOfWeek3)
//output:[2: "Tue", 0: "Sun", 1: "Mon"]
複製代碼
能夠看到字典的鍵值對也是無序的,它與聲明時的順序不必定一致。
// iteration 1
for (key, value) in dayOfWeek {
print("\(key): \(value)")
}
// iteration 2
for key in dayOfWeek.keys {
print(key)
}
// iteration 3
for value in dayOfWeek.values {
print(value)
}
複製代碼
// find value
dayOfWeek = [0: "Sun", 1: "Mon", 2: "Tue"]
if let day = dayOfWeek[2] {
print(day)
}
// addValue 1
dayOfWeek[3] = "Wed"
print(dayOfWeek)
// Prints: [2: "Tue", 0: "Sun", 1: "Mon", 3: "Wed"]
// updateValue 1
dayOfWeek[2] = "Mardi"
print(dayOfWeek)
// Prints: [2: "Mardi", 0: "Sun", 1: "Mon", 3: "Wed"]
// updateValue 2
dayOfWeek.updateValue("Tue", forKey: 2)
print(dayOfWeek)
// Prints: [2: "Tue", 0: "Sun", 1: "Mon", 3: "Wed"]
// removeValue 1
dayOfWeek[1] = nil
print(dayOfWeek)
// Prints: [2: "Tue", 0: "Sun", 3: "Wed"]
// removeValue 2
dayOfWeek.removeValue(forKey: 2)
print(dayOfWeek)
// Prints: [0: "Sun", 3: "Wed"]
// removeAll
dayOfWeek.removeAll()
print(dayOfWeek)
// Output: [:]
複製代碼
能夠看到從字典裏面刪除某個鍵值對有兩個方法:
- 使用
removeValue
方法並傳入要刪除的鍵值對裏的鍵。- 將字典取下標以後將nil賦給它。
這一部份內容主要是對連載的後續文章做鋪墊,讓你們對數據結構先有一個基本的認識,所以在概念上不會深刻講解。該部分由如下三點展開:
數據結構的基本概念
抽象數據類型
鏈表,棧和隊列的實現
首先咱們來看一下數據結構的概念:
數據結構:是相互之間存在一種或多種特定關係的數據元素的集合。
由數據結構這個詞彙的自己(數據的結構)以及它的概念能夠看出,它的重點在於「結構」和「關係」。因此說,數據是何種數據並不重要,重要的是這些數據是如何聯繫起來的。
而這些聯繫,能夠從兩個維度來展開:
能夠看出,邏輯結構是抽象的聯繫,而物理結構是實際在計算機內存裏的具體聯繫。那麼它們本身又細分爲哪些結構呢?
邏輯結構:
物理結構:
爲了便於記憶,用思惟導圖總結一下上面所說的:
而經過結合這兩個維度中的某個結構,能夠定義出來一個實際的數據結構的實現:
好比線性表就是線性結構的一種實現:
爲何會有鏈表這麼麻煩的東西?像數組這樣,全部內存地址都是連續的不是很方便麼?既生瑜何生亮呢?
對於獲取元素(節點)這一操做,使用數組這個數據結構確實很是方便:由於全部元素在內存中是連續的,因此只須要知道數組中第一個元素的地址以及要獲取元素的index就能算出該index內存的地址,一步到位很是方便。
可是對於向數組中某個index中插入新的元素的操做恐怕就沒有這麼方便了:偏偏是由於數組中全部元素的內存是連續的,因此若是想在中間插入一個新的元素,那麼這個位置後面的全部元素都要後移,顯然是很是低效的。若是插在數組尾部還好,若是插在第一位的話成本就過高了。
而若是使用鏈表,只要把要插入到的index先後節點的指針賦給這個新的節點就能夠了,不須要移動原有節點在內存中的位置。
關於鏈表的這種插入操做會在後面用代碼的形式體現出來。
既然有這麼多的數據結構,那麼有沒有一個標準的格式來將這些特定的數據結構(也能夠說是數學模型)抽象出來呢?答案是確定的,它就是咱們下一節要講的抽象數據類型。
首先來看一下抽象數據類型的概念,摘自《大話數據結構》:
抽象數據類型(Abstract Data Type,ADT):是指一個數學模型及定義在該模型上的一組操做。
須要注意的是:抽象數據類型的定義僅僅取決於它的一組邏輯特性,而與其在計算機內部如何表示和實現沒有關係。並且,抽象數據類型不只僅指那些已經定義並實現的數據類型,還尅是計算機編程者本身定義的數據類型。
咱們看一下數據類型的標準格式:
ADT 抽象數據類型名
Data
數據元素之間邏輯關係的定義
Operation
操做1
初始條件
操做結果描述
操做2
初始條件
操做結果描述
操做n
endADT
複製代碼
其實看上去和麪向對象編程裏的類的定義類似:
簡單來講,抽象數據類型描述了一個數據模型所使用的數據和數據之間的邏輯關係,以及它能夠執行的一些操做。所以,若是知道了一個數學模型的抽象數據類型,那麼在真正接觸數學模型的實現(代碼)以前,就能夠對該數學模型能作的事情有一個大體的瞭解。
下一章筆者會介紹鏈表,棧和隊列這三個數學模型,在講解每一個數學模型的實現以前都會給出它們各自的抽象數據類型,讓讀者能夠先對當前數學模型有個大體的瞭解。
注意:書本文概括的全部抽象數據類型是筆者本身根據網上資料和相關書籍而定下來的,因此嚴格來講它們並非「最官方」的抽象數據類型。讀者也能夠參考網上的資料或是相關書籍,結合本身的思考來定義本身對着三個數據模型的抽象數據類型。
經過上一節的介紹,咱們知道了數據結構的概念以及分類,還知道了不一樣的數據結構在不一樣的場景下會發揮不一樣的優點,咱們要根據實際的場景來選擇合適的數據結構。
下面就來介紹幾種在實際應用中使用的比較多的數學模型:
說到鏈表就不得不提線性表這一數據結構,在介紹鏈表以前,首先看一下線性表的定義:
線性表:零個或多個數據元素的有限序列。
而根據物理結構的不一樣,線性表有兩種具體的實現方式:
注:上面兩個概念是筆者用本身的話總結出來的。
在這裏,線性表的順序存儲結構的實現就是咱們熟悉的數組;而線性表的鏈式存儲結構的實現就是筆者即將要介紹的鏈表。
相信對於讀完上一節的朋友來講,應該對鏈表有一個比較清晰的認識了。關於鏈表的定義有不少不一樣的版本,筆者我的比較喜歡百度百科裏的定義:
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。
並且因爲數據元素所持有的指針個數和連接特性能夠將鏈表分爲:
筆者從中挑選出雙向鏈表來進行講解,它的難度適中,並且可以很好地讓讀者體會出鏈表的優點。
由於節點是鏈表的基本組成單元,因此想要實現鏈表,必須先要介紹鏈表的組成部分-節點。
節點:
ADT 節點(node)
Data
value:持有的數據
Operation
init:初始化
previous:指向上一節點的指針
next:指向下一節點的指針
endADT
複製代碼
再來看一下鏈表的抽象數據類型:
ADT 鏈表(linked list)
Data
linked list:持有的線性表
Operation
init:初始化
count:持有節點總個數
isEmpty:是否爲空
first:頭節點
last:尾節點
node:傳入index返回節點
insert:插入node到指定index
insertToHead:插入節點到表頭
appendToTail:插入節點到表尾
removeAll:移除全部節點
remove:移除傳入的節點
removeAt:移除傳入index的節點
endADT
複製代碼
public class LinkedListNode<T> {
//value of a node
var value: T
//pointer to previous node
weak var previous: LinkedListNode?
//pointer to next node
var next: LinkedListNode?
//init
public init(value: T) {
self.value = value
}
}
複製代碼
再來看一下鏈表的實現:
由於整個鏈表的插入,刪除等操做比較多,整個鏈表的定義超過了200行代碼,因此爲了看着方便一點,在這裏來分段說明一下。
首先看一下鏈表的成員變量:
public class LinkedList<T> {
public typealias Node = LinkedListNode<T>
//if empty
public var isEmpty: Bool {
return head == nil
}
//total count of nodes
public var count: Int {
guard var node = head else {
return 0
}
var count = 1
while let next = node.next {
node = next
count += 1
}
return count
}
//pointer to the first node, private
private var head: Node?
//pointer to the first node, public
public var first: Node? {
return head
}
//pointer to the last node
public var last: Node? {
guard var node = head else {
return nil
}
//until node.next is nil
while let next = node.next {
node = next
}
return node
}
...
}
複製代碼
相信看上面的命名以及註釋你們能夠對鏈表的成員變量有個初步的理解,這裏面須要說三點:
typealias
是用來從新爲已經存在的類型命名的:這裏用Node
代替了LinkedListNode<T>
(節點類型),下降了很多閱讀代碼的成本。count
和last
的實現,都先判斷了head
這個指針是否爲nil,若是是則斷定爲空鏈表,天然也就不存在節點個數和最後的節點對象了。count
和last
的實現裏,使用了while
控制語句來判斷node.next節點是否存在:若是存在,則繼續+1或者繼續往下尋找,直到node.next爲nil時才中止。在這裏咱們能夠看到鏈表的尋址方式:是經過頭結點開始,以節點的.next指針來尋找下一個節點的。並且做爲鏈表的尾節點,它的.next指針不指向任何對象,由於它原本就是鏈表的最後一項。最下方的…表明即將在下面介紹的一些函數,這些函數都定義在的
LinkedList
這個class裏面。
//get node of index
public func node(atIndex index: Int) -> Node? {
if index == 0 {
//head node
return head!
} else {
var node = head!.next
guard index < count else {
return nil;
}
for _ in 1..<index {
// go on finding by .next
node = node?.next
if node == nil {
break
}
}
return node!
}
}
複製代碼
注意在這裏返回的node是能夠爲nil的,並且在這裏能夠看出來,鏈表在尋找特定node的時候,是根據節點的.next指針來一個一個尋找的。這個與順序存儲結構的數組是不一樣的,在後面我會重點講解一下這兩者的不一樣。
//insert node to last index
public func appendToTail(value: T) {
let newNode = Node(value: value)
if let lastNode = last {
//update last node: newNode becomes new last node;
//the previous last node becomes the second-last node
newNode.previous = lastNode
lastNode.next = newNode
} else {
//blank linked list
head = newNode
}
}
//insert node to index 0
public func insertToHead(value: T) {
let newHead = Node(value: value)
if head == nil {
//blank linked list
head = newHead
}else {
newHead.next = head
head?.previous = newHead
head = newHead
}
}
//insert node in specific index
public func insert(_ node: Node, atIndex index: Int) {
if index < 0 {
print("invalid input index")
return
}
let newNode = node
if count == 0 {
head = newNode
}else {
if index == 0 {
newNode.next = head
head?.previous = newNode
head = newNode
} else {
if index > count {
print("out of range")
return
}
let prev = self.node(atIndex: index-1)
let next = prev?.next
newNode.previous = prev
newNode.next = prev?.next
prev?.next = newNode
next?.previous = newNode
}
}
}
複製代碼
鏈表的插入節點的操做分爲三種,按照從上到下的順序依次是:
須要注意的是
head
指針。在這裏判斷鏈表爲空鏈表後的處理是筆者本身加上去的,筆者在網上的資料裏沒有看到過。你們沒必要糾結於這種處理方式,畢竟鏈表操做的重點在於先後節點的重連。
//removing all nodes
public func removeAll() {
head = nil
}
//remove the last node
public func removeLast() -> T? {
guard !isEmpty else {
return nil
}
return remove(node: last!)
}
//remove a node by it's refrence
public func remove(node: Node) -> T? {
guard head != nil else {
print("linked list is empty")
return nil
}
let prev = node.previous
let next = node.next
if let prev = prev {
prev.next = next
} else {
head = next
}
next?.previous = prev
node.previous = nil
node.next = nil
return node.value
}
//remove a node by it's index
public func removeAt(_ index: Int) -> T? {
guard head != nil else {
print("linked list is empty")
return nil
}
let node = self.node(atIndex: index)
guard node != nil else {
return nil
}
return remove(node: node!)
}
複製代碼
remove
函數)。在這個函數內部,首先須要將該節點的先後節點對接,而後將該幾點的先後指針置空。removeAt
函數。在這個函數內部,首先根據index來獲取對應的node的指針,而後再調用remove
函數刪除這個node。public func printAllNodes(){
guard head != nil else {
print("linked list is empty")
return
}
var node = head
print("\nstart printing all nodes:")
for index in 0..<count {
if node == nil {
break
}
print("[\(index)]\(node!.value)")
node = node!.next
}
}
複製代碼
該函數只是爲了方便調試,爲了跟蹤鏈表的狀態而定義的,它並不存在於鏈表的模型裏。
爲了驗證上面這些方法的有效性,咱們來實例化一個鏈表後實際操做一下,讀者能夠結合註釋來看一下每一步對應的結果:
let list = LinkedList<String>()
list.isEmpty // true
list.first // nil
list.count // 0
list.appendToTail(value: "Swift")
list.isEmpty // false
list.first!.value // "Swift"
list.last!.value // "Swift"
list.count //1
list.appendToTail(value:"is")
list.first!.value // "Swift"
list.last!.value // "is"
list.count // 2
list.appendToTail(value:"great")
list.first!.value // "Swift"
list.last!.value // "great"
list.count // 3
list.printAllNodes()
//[0]Swift
//[1]is
//[2]Great
list.node(atIndex: 0)?.value // Swift
list.node(atIndex: 1)?.value // is
list.node(atIndex: 2)?.value // great
list.node(atIndex: 3)?.value // nil
list.insert(LinkedListNode.init(value: "language"), atIndex: 1)
list.printAllNodes()
//[0]Swift
//[1]language
//[2]is
//[3]great
list.remove(node: list.first!)
list.printAllNodes()
//[0]language
//[1]is
//[2]great
list.removeAt(1)
list.printAllNodes()
//[0]language
//[1]great
list.removeLast()
list.printAllNodes()
//[0]language
list.insertToHead(value: "study")
list.count // 2
list.printAllNodes()
//[0]study
//[1]language
list.removeAll()
list.printAllNodes()//linked list is empty
list.insert(LinkedListNode.init(value: "new"), atIndex: 3)
list.printAllNodes()
//[0]new
list.insert(LinkedListNode.init(value: "new"), atIndex: 3) //out of range
list.printAllNodes()
//[0]new
list.insert(LinkedListNode.init(value: "new"), atIndex: 1)
list.printAllNodes()
//[0]new
//[1]new
複製代碼
棧的講解從
三個部分來展開。
首先來看一下棧的定義:
棧是限定僅在表的尾部進行插入和刪除操做的線性表。
從定義中能夠看出,咱們知道咱們只能在棧的一端來操做棧:
用一張圖來看一下棧的操做:
圖源:《維基百科:Stack (abstract data type)》
從上圖能夠看出,最早壓入棧裏面的只能最後訪問,也就是說,棧遵循後進先出(Last In First Out, LIFO)的原則。
ADT 棧(Stack)
Data
linked list:持有的線性表
Operation
init:初始化
count:棧的元素個數
isEmpty:是否爲空
push:入棧
pop:出棧
top:返回頂部元素
endADT
複製代碼
上面的operation可能不全,可是涵蓋了棧的一些最基本的操做。那麼基於這個抽象數據類型,咱們來看一下如何使用Swift來實現它。
筆者將數組(順序存儲)做爲棧的線性表的實現,同時支持泛型。
public struct Stack<T> {
//array
fileprivate var stackArray = [T]()
//count
public var count: Int {
return stackArray.count
}
//is empty ?
public var isEmpty: Bool {
return stackArray.isEmpty
}
//top element
public var top: T? {
if isEmpty{
return nil
}else {
return stackArray.last
}
}
//push operation
public mutating func push(_ element: T) {
stackArray.append(element)
}
//pop operation
public mutating func pop() -> T? {
if isEmpty{
print("stack is empty")
return nil
}else {
return stackArray.removeLast()
}
}
//print all
public mutating func printAllElements() {
guard count > 0 else {
print("stack is empty")
return
}
print("\nprint all stack elemets:")
for (index, value) in stackArray.enumerated() {
print("[\(index)]\(value)")
}
}
}
複製代碼
fileprivate
:是Swift3.0新增的訪問控制,表示在定義的聲明文件裏可訪問。它代替了過去意義上的private
。而有了fileprivate
之後,新的private
則表明了真正的私有:在這個類或結構體的外部沒法訪問。printAllElements
方法也不屬於抽象數據類型裏的方法,也是爲了方便調試,能夠打印出全部的數據元素。咱們來實例化上面定義的棧實際操做一下:
var stack = Stack.init(stackArray: [])
stack.printAllElements() //stack is empty
stack.isEmpty //true
stack.push(2)
stack.printAllElements()
//[0]2
stack.isEmpty //false
stack.top //2
stack.push(3)
stack.printAllElements()
//[0]2
//[1]3
stack.isEmpty //false
stack.top //3
stack.pop()
stack.printAllElements()
//[0]2
stack.isEmpty //false
stack.top //2
stack.pop()
stack.printAllElements() //stack is empty
stack.top //nil
stack.isEmpty //true
stack.pop() //stack is empty
複製代碼
隊列的講解從
三個部分來展開。
圖源:《維基百科:FIFO (computing and electronics)》
ADT 隊列(Queue)
Data
linked list:持有的線性表
Operation
init:初始化
count:棧的元素個數
isEmpty:是否爲空
front:獲取隊列頭元素
enqueue:插入到隊尾
dequeue:刪除隊列頭元素並返回
endADT
複製代碼
和上面的棧的實現一致,隊列的實現也使用數組來實現隊列內部的線性表。
public struct Queue<T> {
//array
fileprivate var queueArray = [T]()
//count
public var count: Int {
return queueArray.count
}
//is empty?
public var isEmpty: Bool {
return queueArray.isEmpty
}
//front element
public var front: T? {
if isEmpty {
print("queue is empty")
return nil
} else {
return queueArray.first
}
}
//add element
public mutating func enqueue(_ element: T) {
queueArray.append(element)
}
//remove element
public mutating func dequeue() -> T? {
if isEmpty {
print("queue is empty")
return nil
} else {
return queueArray.removeFirst()
}
}
//print all
public mutating func printAllElements() {
guard count > 0 else {
print("queue is empty")
return
}
print("\nprint all queue elemets:")
for (index, value) in queueArray.enumerated() {
print("[\(index)]\(value)")
}
}
}
複製代碼
咱們初始化一個隊列後實際操做一下:
var queue = Queue.init(queueArray: [])
queue.printAllElements()//queue is empty
queue.isEmpty //true
queue.count //0
queue.enqueue(2)
queue.printAllElements()
queue.isEmpty //false
//[0]2
queue.enqueue(3)
queue.printAllElements()
//[0]2
//[1]3
queue.enqueue(4)
queue.printAllElements()
//[0]2
//[1]3
//[2]4
queue.front //2
queue.dequeue()
queue.printAllElements()
//[0]3
//[1]4
queue.front //3
queue.dequeue()
queue.printAllElements()
//[0]4
queue.front //4
queue.dequeue()
queue.printAllElements() //queue is empty
queue.front //return nil, and print : queue is empty
queue.isEmpty //true
queue.count//0
複製代碼
這兩週學習數據結構和算法讓我收穫不少,除了強化了Swift語法之外,感受本身看代碼的感受變了:看到一個設計就會想到裏面所用到的數據結構,或是算法上面有沒有能夠優化的可能等等。
我相信對我來講編程的一扇新的門被打開了,但願本身能夠堅持下去,看到更廣闊的世界。
該系列的全部代碼會放在個人GitHub的一個項目裏面,項目地址:Github:data-structure-and-algorithm-in-Swift
本篇文章的代碼:
下篇預告:
從下一篇會開始正式講解算法。本系列第二篇的主題是排序算法,內容是用Swift語言實現並講解幾種比較常見的排序算法:冒泡排序,選擇排序,插入排序,希爾排序,堆排序,快速排序。
本文已同步到我的博客:傳送門
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~