[Swift]八大排序算法(二):快速排序
排序分為內部排序和外部排序。
內部排序:是指待排序列完全存放在內存中所進行的排序過程,適合不太大的元素序列。
外部排序:指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件無法一次裝入內存,需要在內存和外部存儲器之間進行多次數據交換,以達到排序整個文件的目的。
當N小於20的時候,插入排序具有最好的性能。
當N大於20時,快速排序具有最好的性能,盡管歸並(merge sort)和堆排序(heap sort)復雜度都為nlog2(n)。
快速排序算法:快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它采用了一種分治的策略,通常稱其為分治法(Divide-and-ConquerMethod)。
(1)分治法的基本思想:將原問題分解為若幹個規模更小但結構與原問題相似的子問題。遞歸地解這些子問題,然後將這些子問題的解組合為原問題的解。
(2)快速排序的基本思想:設當前待排序的無序區為R[low..high],利用分治法可將快速排序的基本思想描述為:
①分解:
在R[low..high]中任選一個記錄作為基準(Pivot),以此基準將當前無序區劃分為左、右兩個較小的子區間R[low..pivotpos-1)和R[pivotpos+1..high],並使左邊子區間中所有記錄的關鍵字均小於等於基準記錄(不妨記為pivot)的關鍵字pivot.key,右邊的子區間中所有記錄的關鍵字均大於等於pivot.key,而基準記錄pivot則位於正確的位置(pivotpos)上,它無須參加後續的排序。註意:劃分的關鍵是要求出基準記錄所在的位置pivotpos。
劃分的結果可以簡單地表示為(註意pivot=R[pivotpos]) :R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通過遞歸調用快速排序對左、右子區間R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③組合:
因為當"求解"步驟中的兩個遞歸調用結束時,其左、右兩個子區間已有序。對快速排序而言,"組合"步驟無須做什麽,可看作是空操作。
快速排序具有最好的平均性能(average behavior),但最壞性能(worst case behavior)和插入排序相同,也是O(n^2)。比如一個序列5,4,3,2,1,要排為1,2,3,4,5。按照快速排序方法,每次只會有一個數據進入正確順序。
ViewController.swift文件:運行時間(4.6361s)
1 import UIKit 2 3 //對數組類型進行擴展 4 extension Array 5 { 6 //擴展方法:用來交換數組中的兩個位置的元素 7 fileprivate mutating func swap(i:Int,j:Int) 8 { 9 //通過一個臨時變量,交換數組中的兩個不同位置的元素 10 let temp = self[i] 11 self[i] = self[j] 12 self[j] = temp 13 } 14 } 15 16 //對具有可比較性的數組進行擴展 17 //以實現快速排序功能 18 extension Array where Element:Comparable 19 { 20 //添加i一個擴展方法,用來實現具體的排序功能 21 func quickSort(list: inout Array<Int>, low: Int, high: Int) 22 { 23 //首先將需要排序的數組首次分成兩個部分, 24 if low < high 25 { 26 //並返回將數組進行分隔成兩個部分的中間值, 27 //位於中間值左側的數據都比它小, 28 //右側的數據都比它大。 29 //此方法在下方的代碼中實現 30 let mid = partition(list: &list, low: low, high: high) 31 //接著通過遞歸的方式, 32 //對左右兩側的數組進行統一的排序, 33 //最終將獲得一個有序的數組 34 quickSort(list: &list, low: low, high: mid - 1) 35 quickSort(list: &list, low: mid + 1, high: high) 36 } 37 } 38 39 //添加一個方法,用來將數組一分為二, 40 //並返回一個中間值, 41 //位於中間值左側的數據都比它小, 42 //右側的數據都比它大 43 private func partition(list: inout Array<Int>, low: Int, high: Int) -> Int { 44 //將數組中的最左側的值作為臨時常量,對數組進行分隔 45 var low = low 46 var high = high 47 let temp = list[low] 48 //添加一個指定區間的循環語句 49 while low < high 50 { 51 //繼續添加一個循環語句,從數組的尾部向頭部進行遍歷, 52 //用來獲得比臨時值小的元素所在的位置 53 while low < high && list[high] >= temp 54 { 55 high -= 1 56 } 57 //然後將該元素的值,替代原來的最小值 58 list[low] = list[high] 59 //繼續添加一個循環語句,從數組的頭部, 60 //向上一個循環語句找到的, 61 //比臨時值小的元素所在的索引位置 62 //用來獲得比臨時值小的元素所在的位置 63 while low < high && list[low] <= temp 64 { 65 low += 1 66 } 67 //然後將該元素的值,替代原來的最大值 68 list[high] = list[low] 69 } 70 //這樣就完成了對數組分成兩部分的任務, 71 //然後將用來分割值的索引, 72 //替換low(此時low和high是相等的)索引所在的位置的元素即可。 73 list[low] = temp 74 //最後返回low作為分割值的索引 75 return low 76 } 77 } 78 79 class ViewController: UIViewController { 80 //上面是選擇排序算法的編寫。 81 //現在通過可視化的方式,使用冒泡排序算法 82 83 //屬性1:用來存儲需要排序的數組 84 var result : Array<Int> = Array<Int>() 85 //屬性2:統計排序花費的時間 86 var date : Date! 87 88 override func viewDidLoad() { 89 super.viewDidLoad() 90 // Do any additional setup after loading the view, typically from a nib. 91 //初始化一個整形數組 92 var array : Array<Int> = Array<Int>() 93 //將1至100的100個整數,存入到該數組中 94 for i in 1...100 95 { 96 array.append(i) 97 } 98 //添加一個循環語句, 99 //用來生成一個由100個隨機整數組成的數組 100 for _ in 1...100 101 { 102 //首先根據數組的長度, 103 //獲得一個1至100的隨機整數 104 let temp = Int(arc4random() % UInt32(array.count))+1 105 //根據隨機值從數組中獲得指定位置的整數, 106 //並存儲在用來排序的數組中 107 let num = array[temp-1] 108 result.append(num) 109 //從原數組中移該隨機數,以避免獲得重復的數字 110 array.remove(at: temp-1) 111 } 112 //添加一個循環語句, 113 //用來生成100個自定義視圖對象 114 for i in 1...100 115 { 116 //初始化自定義視圖對象 117 let num = result[i-1] 118 //並設置它的顯示區域。 119 //其中視圖的高度,是當前數組中的數字的兩倍大小 120 let view = SortView(frame: CGRect(x: 10+i*3, y: 200, width: 2, height: num * 2)) 121 view.backgroundColor = .black 122 //設置視圖的標識值 123 view.tag = i 124 //並將視圖添加到當前視圖控制器的根視圖 125 self.view.addSubview(view) 126 } 127 //然後添加一個按鈕 128 //當用戶點擊該按鈕時對數組進行排序 129 let bt = UIButton(frame: CGRect(x: 10, y: 340, width: 300, height: 40)) 130 //設置背景按鈕的背景顏色為橙色 131 bt.backgroundColor = .orange 132 //設置按鈕在正常狀態下的標題文字 133 bt.setTitle("Sort", for: .normal) 134 //給按鈕對象綁定點擊事件, 135 bt.addTarget(self, action: #selector(reOrderView), for: .touchUpInside) 136 //將按鈕添加到當前視圖控制器的根視圖 137 self.view.addSubview(bt) 138 } 139 140 //添加一個方法,用來響應按鈕的點擊事件 141 @objc func reOrderView() 142 { 143 //獲得當前的日期和時間 144 date = Date() 145 //在一個全局隊列中,以異步的方式對數組進行排序 146 //並實時調整和數組中的數值相對應的視圖的位置 147 DispatchQueue.global().async 148 { 149 //調用實例方法,用來進行可視化的選擇排序。 150 //該方法在下方的代碼中實現 151 self.quickSort(list: &self.result, low: 0, high: self.result.count-1) 152 //獲得排序後的系統時間, 153 //並在控制臺輸出兩個時間的差值, 154 //從而獲得排序所花費的大致時間。 155 //考慮線程休眠的影響,此數據僅做參考 156 let endDate = Date() 157 print(endDate.timeIntervalSince(self.date)) 158 } 159 } 160 161 //添加一個方法,用來實現具體可視化的快速排序的功能 162 func quickSort(list: inout Array<Int>, low: Int, high: Int) 163 { 164 //首先將需要排序的數組首次分成兩個部分, 165 if low < high 166 { 167 //並返回將數組進行分隔成兩個部分的中間值, 168 //位於中間值左側的數據都比它小, 169 //右側的數據都比它大。 170 //此方法在下方的代碼中實現 171 let mid = partition(list: &list, low: low, high: high) 172 //接著通過遞歸的方式, 173 //對左右兩側的數組進行統一的排序, 174 //最終將獲得一個有序的數組 175 quickSort(list: &list, low: low, high: mid - 1) 176 quickSort(list: &list, low: mid + 1, high: high) 177 } 178 } 179 180 //並返回一個中間值, 181 //位於中間值左側的數據都比它小, 182 //右側的數據都比它大 183 private func partition(list: inout Array<Int>, low: Int, high: Int) -> Int { 184 //將數組中的最左側的值作為臨時常量,對數組進行分隔 185 var low = low 186 var high = high 187 let temp = list[low] 188 //添加一個指定區間的循環語句 189 while low < high 190 { 191 //繼續添加一個循環語句,從數組的尾部向頭部進行遍歷, 192 //用來獲得比臨時值小的元素所在的位置 193 while low < high && list[high] >= temp 194 { 195 high -= 1 196 } 197 //然後將該元素的值,替代原來的最小值 198 list[low] = list[high] 199 //同時調用另外一個實例方法, 200 //同步更新界面中的對應的視圖對象的位置 201 udpateView(j: low, height: list[high]) 202 203 //繼續添加一個循環語句,從數組的頭部, 204 //向上一個循環語句找到的, 205 //比臨時值小的元素所在的索引位置 206 //用來獲得比臨時值小的元素所在的位置 207 while low < high && list[low] <= temp 208 { 209 low += 1 210 } 211 //然後將該元素的值,替代原來的最大值 212 list[high] = list[low] 213 //同時調用另外一個實例方法, 214 //同步更新界面中的對應的視圖對象的高度 215 udpateView(j: high, height: list[low]) 216 } 217 //這樣就完成了對數組分成兩部分的任務, 218 //然後將用來分割值的索引, 219 //替換low(此時low和high是相等的)索引所在的位置的元素即可。 220 list[low] = temp 221 //同時調用另外一個實例方法, 222 //同步更新界面中的對應的視圖對象的高度 223 udpateView(j: low, height: temp) 224 //最後返回low作為分割值的索引 225 return low 226 } 227 228 //添加一個方法,用來同步更新界面中視圖的高度 229 func udpateView(j: Int, height: Int) 230 { 231 //由於需要對界面元素進行調整, 232 //所以需要切換至主線程 233 weak var weak_self = self 234 DispatchQueue.main.async 235 { 236 //根據標識值,獲得需要更換高度的視圖對象 237 //然後修改其值為指定的高度 238 let view = weak_self?.view.viewWithTag(j+1) 239 view?.frame.size.height = CGFloat(height*2) 240 } 241 //使線程休眠0.01秒, 242 //以方便觀察排序的視覺效果 243 Thread.sleep(forTimeInterval: 0.01) 244 } 245 246 override func didReceiveMemoryWarning() { 247 super.didReceiveMemoryWarning() 248 // Dispose of any resources that can be recreated. 249 } 250 }
SortView.swift文件
1 import UIKit 2 3 class SortView: UIView { 4 //首先重寫父類的初始化方法 5 override init(frame: CGRect) 6 { 7 //設置自定義視圖對象的顯示區域 8 super.init(frame: frame) 9 self.frame = frame 10 } 11 12 //添加一個必須實現的初始化方法 13 required init?(coder aDecoder: NSCoder) { 14 fatalError("init(coder:) has not been implemented") 15 } 16 17 //重寫父類的重新布局子視圖方法 18 //將在此視圖中對視圖進行外觀設置 19 override func layoutSubviews() 20 { 21 //首先獲得自定義視圖在界面中對Y軸坐標 22 let y: CGFloat = 300 - frame.height 23 //然後重新設置自定義視圖的位置 24 self.frame = frame 25 self.frame.origin.y = y 26 //根據自定義視圖的高度,計算一個權重數值 27 //用於生成不同的背景顏色 28 let weight = frame.height / 200 29 //生成不同y色相的顏色對象,從而給自定義視圖設置不同的背景顏色 30 //然後打開ViewController.swift文件 31 let color = UIColor(hue: weight, saturation: 1, brightness: 1, alpha: 1) 32 self.backgroundColor = color 33 } 34 /* 35 // Only override draw() if you perform custom drawing. 36 // An empty implementation adversely affects performance during animation. 37 override func draw(_ rect: CGRect) { 38 // Drawing code 39 } 40 */ 41 }
[Swift]八大排序算法(二):快速排序