排序是咱們的平常開發中常常會遇到的需求,例如,在商品的列表頁面,咱們能夠根據各類維度(銷量、價格、人氣等)對商品的展現順序進行改變。算法
因此,對各個排序的性能的瞭解也是基礎且重要的。咱們先對排序這一塊進行一個總體的把握。數組
關於排序的具體過程,本文沒有利用圖示來具體說明,可是在代碼中對每一步執行加上了一些 log,有興趣的能夠複製代碼到本身的 Playgroud 中運行查看。bash
基本思想app
兩兩比較相鄰記錄,若是反序則交換,直到沒有反序的記錄爲止。函數
代碼實現性能
func bubbleSort(_ items: [Int]) -> [Int] {
var arr = items
for i in 0..<arr.count {
print("第\(i + 1)輪冒泡:")
var j = arr.count - 1
while j > i {
if arr[j-1] > arr[j] {
print("\(arr[j-1]) > \(arr[j]): 進行交換")
arr.swapAt(j-1, j)
} else {
print("\(arr[j-1]) <= \(arr[j]): 不進行交換")
}
j -= 1
}
print("第\(i + 1)輪冒泡結束")
print("當前結果爲:\n\(arr)\n")
}
return arr
}
複製代碼
根據上面的實現過程,能夠發現若是數組是相似於 [2, 1, 3, 4, 5] 的話,兩次冒泡以後,數組就已經調整爲 [1, 2, 3, 4, 5]。此時,再執行後面的循環實際上是多餘了,因此咱們在發現冒泡循環沒有進行交換的話,說明排序就已經完成了。下面咱們能夠將代碼優化一下:優化
func bubbleSort_v2(_ items: [Int]) -> [Int] {
var arr = items
var flag = true
for i in 0..<arr.count {
print("第\(i + 1)輪冒泡:")
if !flag {
return []
}
flag = false
var j = arr.count - 1
while j > i {
if arr[j-1] > arr[j] {
print("\(arr[j-1]) > \(arr[j]): 進行交換")
arr.swapAt(j-1, j)
flag = true
} else {
print("\(arr[j-1]) <= \(arr[j]): 不進行交換")
}
j -= 1
}
print("第\(i + 1)輪冒泡結束")
print("當前結果爲:\n\(arr)\n")
}
return arr
}
複製代碼
基本思想ui
每一次從待排序的數據元素中選擇最小(或最大)的一個元素放在已排好序列的末尾,直到全部元素排完爲止。spa
代碼實現code
func selectSort(_ items: [Int]) -> [Int] {
var arr = items
for i in 0..<arr.count {
print("第\(i+1)輪選擇,x選擇下標的範圍爲\(i)----\(arr.count)")
var j = i + 1
var minValue = arr[i]
var minIndex = i
// 尋找無序部分中最小值
while j < arr.count {
if minValue > arr[j] {
minValue = arr[j]
minIndex = j
}
j = j + 1
}
print("在後半部分亂序數列中,最小值爲:\(minValue),下標爲:\(minIndex)")
// 與無序表中的第一個值交換,讓其成爲有序表中的最後一個值
if minIndex != i {
arr.swapAt(minIndex, i)
}
print("本輪結果爲:\(arr)\n")
}
return arr
}
複製代碼
基本思想
每一步將一個待排序的記錄,插入到前面已經排好序的有序序列中去,直到插完全部元素爲止,最終能夠獲得一個有序表。
代碼實現
func insertSort(_ items: [Int]) -> [Int] {
var arr = items
for i in 0..<arr.count {
print("第\(i)輪插入:")
print("要選擇插入的值爲:\(arr[i])")
var j = i
while j > 0 {
if arr[j] < arr[j-1] {
arr.swapAt(j, j-1)
j -= 1
} else {
break
}
}
print("插入的位置:\(j)")
print("本輪插入完畢,插入結果爲:\n\(arr)\n")
}
return arr
}
複製代碼
基本思想
其實希爾排序是插入排序的升級版,希爾排序根據其排序的特色又叫作縮小增量排序。希爾排序的大致步驟就是先將無序序列按照必定的步長(增量)分爲幾組,分別將這幾組中的數據經過插入排序的方式將其進行排序。而後縮小步長(增量)分組,而後將組內的數據再次進行排序。直到增量爲 1 位置。通過上述這些步驟,咱們的序列就是有序的了。其實上述的插入排序就是增量爲 1 的希爾排序。
代碼實現
func hillSort(_ items: [Int]) -> [Int] {
var arr = items
var step: Int = arr.count / 2
while step > 0 {
print("步長爲\(step)的插入排序開始:")
for i in 0..<arr.count {
var j = i + step
while j >= step && j < arr.count {
if arr[j] < arr[j-step] {
arr.swapAt(j, j-step)
j = j - step
} else {
break
}
}
}
print("步長爲\(step)的插入排序結束")
print("本輪排序結果爲:\(arr)\n")
step = step / 2 // 縮小步長
}
return arr
}
複製代碼
準備知識
堆 是具備如下性質的徹底二叉樹:每一個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每一個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。
咱們對堆中的結點按層進行編號,將這種邏輯結構映射到數組中就是下面這個樣子。
大頂堆: arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小頂堆: arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
基本思想
將待排序序列構形成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。而後將剩餘 n-1 個元素從新構形成一個堆,這樣會獲得 n 個元素的次小值。如此反覆執行,便能獲得一個有序序列了。
基本步驟
代碼實現
// 構建小頂堆的層次遍歷序列
func heapCreate(items: inout Array<Int>) {
var i = items.count
while i > 0 {
heapAdjast(items: &items, startIndex: i - 1, endIndex: items.count)
i -= 1
}
}
// 對小頂堆j的局部進行調整, 使其該節點的全部父類符合小頂堆的特色
func heapAdjast(items: inout Array<Int>, startIndex: Int, endIndex: Int) {
let temp = items[startIndex]
var fatherIndex = startIndex + 1 //父節點下標
var maxChildIndex = 2 * fatherIndex //左孩子下標
while maxChildIndex <= endIndex {
if maxChildIndex < endIndex && items[maxChildIndex-1] < items[maxChildIndex] {
maxChildIndex = maxChildIndex + 1
}
if temp < items[maxChildIndex-1] {
items[fatherIndex-1] = items[maxChildIndex-1]
} else {
break
}
fatherIndex = maxChildIndex
maxChildIndex = 2 * fatherIndex
}
items[fatherIndex-1] = temp
}
func heapSort(_ items: [Int]) -> [Int] {
var arr = items
var endIndex = arr.count - 1
print("原始堆")
print(arr)
// 建立小頂堆,其實就是講arr轉換成小頂堆層次的遍歷結果
heapCreate(items: &arr)
print(arr)
print("堆排序開始")
while endIndex > 0 {
// 將小頂堆的定點與小頂堆的最後一個值進行交換
arr.swapAt(endIndex, 0)
endIndex -= 1 // 縮小小頂堆的範圍
// 對交換後的小頂堆進行調整,使其從新成爲小頂堆
print("從新調整")
heapAdjast(items: &arr, startIndex: 0, endIndex: endIndex + 1)
print("調整後的結果以下")
print(arr)
}
return arr
}
複製代碼
基本思想
歸併排序是利用歸併的思想實現的排序方法,該算法採用經典的分治策略。
將須要排序的數組分解爲一個個單獨的子元素,兩兩比較後將其合併爲一個有序數組,不斷重複比較及合併的過程,直至合併以後的有序數組的元素數量與原數組相同時,則排序結束。
代碼實現
// 合併兩個數組
func mergeArray(firstList: Array<Int>, secondList: Array<Int>) -> Array<Int> {
var resultList: Array<Int> = []
var firstIndex = 0
var secondIndex = 0
// 拿出兩數組第一個元素,比較以後取小的插入新數組
while firstIndex < firstList.count && secondIndex < secondList.count {
if firstList[firstIndex] < secondList[secondIndex] {
resultList.append(firstList[firstIndex])
firstIndex += 1
} else {
resultList.append(secondList[secondIndex])
secondIndex += 1
}
}
// 將第一個數組剩下的元素插入新數組
while firstIndex < firstList.count {
resultList.append(firstList[firstIndex])
firstIndex += 1
}
// 將第二個數組剩下的元素插入新數組
while secondIndex < secondList.count {
resultList.append(secondList[secondIndex])
secondIndex += 1
}
return resultList
}
func mergeSort(_ items: [Int]) -> [Int] {
let arr = items
// 將數組中的每個元素放入一個數組中
var tempArray: Array<Array<Int>> = []
for item in arr {
var subArray: Array<Int> = []
subArray.append(item)
tempArray.append(subArray)
}
// 對這個數組中的數組進行合併,直到合併完畢爲止
while tempArray.count != 1 {
print(tempArray)
var i = 0
while i < tempArray.count - 1 {
print("將\(tempArray[i])與\(tempArray[i+1])合併")
tempArray[i] = mergeArray(firstList: tempArray[i], secondList: tempArray[i+1])
print("合併結果爲:\(tempArray[i])\n")
tempArray.remove(at: i + 1)
i = i + 1
}
}
return tempArray.first!
}
複製代碼
基本思想
快速排序(Quick Sort)使用分治法策略。
選擇一個基準數,經過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的全部數據都比另一部分的全部數據都要小。而後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。
快速排序流程:
代碼實現
/// 將數組以第一個值爲準分爲兩部分,前半部分比該值要小,後半部分比該值要大
///
/// - parameter list: 要二分的數組
/// - parameter low: 數組的下界
/// - parameter high: 數組的上界
///
/// - return: 分界點
func partition(list: inout Array<Int>, low: Int, high: Int) -> Int {
var low = low
var high = high
let temp = list[low]
while low < high {
while low < high && list[high] >= temp {
high -= 1
}
list[low] = list[high]
while low < high && list[low] <= temp {
low += 1
}
list[high] = list[low]
print("====low==\(low)======high==\(high)")
}
list[low] = temp
print("=====temp==\(temp)")
print(list)
return low
}
/// 快速排序
///
/// - parameter list: 要排序的數組
/// - parameter low: 數組的下界
/// - parameter high: 數組的上界
func quickSortHelper(list: inout Array<Int>, low: Int, high: Int) {
if low < high {
let mid = partition(list: &list, low: low, high: high)
quickSortHelper(list: &list, low: low, high: mid - 1)
quickSortHelper(list: &list, low: mid + 1, high: high)
}
}
func quickSort(_ items: Array<Int>) -> Array<Int> {
var list = items
quickSortHelper(list: &list, low: 0, high: list.count - 1)
print("快速排序結束")
return list
}
複製代碼
在Swift源代碼中,sort 函數採用的是一種內審算法(IntroSort)。它由堆排序、插入排序、快速排序三種算法構成,依據輸入的深度相應選擇最佳的算法來完成。
// 以升序排列爲例,array 數組可改變
array.sort
// 以降序排列爲例,array 數組不可改變
newArray = array.sorted(by: >)
複製代碼
排序方法 | 時間複雜度 | 輔助空間 |
---|---|---|
冒泡排序 | O(n²) | O(1) |
選擇排序 | O(n²) | O(1) |
插入排序 | O(n²) | O(1) |
希爾排序 | 與增量選取策略有關 | O(1) |
堆排序 | O(nlogn) | O(1) |
歸併排序 | O(nlogn) | O(n) |
快速排序 | O(nlogn) | O(logn)-O(n) |