一、冒泡排序
是一種簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果它們的順序錯誤就把它們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
操作步驟:
- 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
- 針對所有的元素重復以上的步驟,除了最后一個。
- 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
平均時間復雜度:O(n2);最壞時間復雜度:O(n2);最好時間復雜度:O(n);空間復雜度:O(1);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不變)
實現代碼(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } var arr = list for i in 0..<list.count-1 { // 只遍歷開頭至倒數第二個(除了最后一個) for j in 0..<list.count-1-i { // 分別對每個元素進行比較(這里-i的目的是排除比較后的元素) if arr[j] > arr[j+1] { // 交換元素,這里采用數學算法(僅適用於數字),>:表示升序;<:表示降序 arr[j] += arr[j+1] arr[j+1] = arr[j] - arr[j+1] arr[j] = arr[j] - arr[j+1] } } } return arr } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
二、選擇排序
是一種簡單直觀的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
操作步驟:
- 選擇比較的起始位置(一般從0開始)分別遍歷每個元素,與其他元素做比較,選出最小或最大的元素
- 將最小或最大的元素與當前比較的起始位置進行交換,起始位置+1
- 重復以上步驟,直到比較結束
平均時間復雜度:O(n2);最壞時間復雜度:O(n2);最好時間復雜度:O(n2);空間復雜度:O(1);穩定性:不穩定(即:排序前兩個相同元素,在排序后前后順序發生了變化)
實現代碼(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } var arr = list for i in 0..<list.count-1 { // 只遍歷開頭至倒數第二個(除了最后一個) var idx = i // 最小或最大值的索引 for j in i+1..<list.count { // 分別對每個元素進行比較 if arr[j] < arr[idx] { // 找到與當前idx比較,最小(升序)(>:最大,降序)的那個元素下標 idx = j } } if i != idx { // 交換兩個元素 arr[i] += arr[idx] arr[idx] = arr[i] - arr[idx] arr[i] = arr[i] - arr[idx] } } return arr } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
三、插入排序
是一種簡單直觀的排序算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。插入排序在實現上,通常采用in-place排序(即只需用到O(1)的額外空間的排序),因而在從后向前掃描過程中,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。
操作步驟:
- 從第一個元素開始,該元素可以被認為是排好序的序列
- 取出下一個元素a,分別與該排好序的序列的每個元素(從后向前)比較
- 如果已排好序中的元素b大於當前取出的元素a,將b后移一位
- 重復3的步驟,直到已排好序中的元素c小於或等於當前取出的元素a
- 將當前取出的元素a插入到元素c之后
- 重復2-5,直到遍歷結束
平均時間復雜度:O(n2);最壞時間復雜度:O(n2);最好時間復雜度:O(n);空間復雜度:O(1);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不變)
實現代碼(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } var arr = list for i in 1..<list.count { // 從第二個元素開始遍歷后續的每個元素 var j = i - 1 // 排好序的有序序列中的最后一個元素 let k = arr[i] // 當前取出的元素 while j >= 0 && arr[j] > k { // 從有序序列中最后一個元素開始向前遍歷,>:升序;<:降序 arr[j+1] = arr[j] // 將有序序列中的元素逐個后移 j -= 1 } arr[j+1] = k // 插入取出的元素 } return arr } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
四、希爾排序
也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。它與插入排序的不同之處在於,它會優先比較距離較遠的元素。
希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的性能。這樣可以讓一個元素可以一次性地朝最終位置前進一大步。然后算法再取越來越小的步長進行排序,算法的最后一步就是普通的插入排序,但是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。
步長的選擇是希爾排序的重要部分。只要最終步長為1任何步長序列都可以工作。
已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項,從第0項開始,偶數來自9x4i - 9x2i + 1,和奇數來自2i+2 x (2i+2 - 3) + 1這兩個算式。用這樣步長序列的希爾排序比插入排序要快,甚至在小數組中比快速排序和堆排序還快,但是在涉及大量數據時希爾排序還是比快速排序慢。
操作步驟:
- 計算增量值,即步長,一般采用n/2,並對步長取半直到步長達到1
- 根據步長將待排序的序列分成若干組,分別對每組進行插入排序
- 重復1-2,直到步長為1結束
平均時間復雜度:O(n1.3);最壞時間復雜度:O(n2);最好時間復雜度:O(n);空間復雜度:O(1);穩定性:不穩定(即:排序前兩個相同元素,在排序后前后順序發生了改變)
實現代碼(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } var arr = list var stepSize = list.count >> 1 // 步長 while stepSize > 0 { for i in stepSize..<list.count { var j = i let currVal = arr[i]
while j-stepSize >= 0, currVal < arr[j-stepSize] { // currVal <:升序;currVal >:降序 arr[j] = arr[j-stepSize] j -= stepSize } arr[j] = currVal } stepSize >>= 1 } return arr } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
五、歸並排序
該算法是采用分治法(Divide and Conquer)的一個非常典型的應用,且各層分治遞歸可以同時進行。實際上就是將一個序列分成若干個子序列,使其子序列有序后,將子序列合並,再次得到一組子序列,重復合並,直到成為一個序列。
歸並排序離不開歸並算法,歸並算法指的是將兩個已經排序的序列合並成一個序列的算法。
根據遍歷方式不同,可以分為兩種操作方法:
遞歸法:
- 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
- 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
- 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
- 重復步驟3直到某一指針到達序列尾
- 將另一序列剩下的所有元素直接復制到合並序列尾
迭代法:
- 將序列每相鄰兩個數字進行歸並操作,形成ceil(n/2)個序列,排序后每個序列包含兩/一個元素
- 若此時序列數不是1個則將上述序列再次歸並,形成ceil(n/4)個序列,每個序列包含四/三個元素
- 重復步驟2,直到所有元素排序完畢,即序列數為1
平均時間復雜度:O(n * log n);最壞時間復雜度:O(n * log n);最好時間復雜度:O(n * log n);空間復雜度:O(n);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不變)
實現代碼(swift):
迭代法:
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } let len = list.count var a = list // 用於排序的序列 var arr = list // 用於臨時存儲歸並后的元素 var seg = 1 // 分割下標 while seg < len { var start = 0 while start < len { var k = start let l = start let m = min(start + seg, len) let h = min(start + seg * 2, len) var start1 = l, end1 = m var start2 = m, end2 = h while start1 < end1, start2 < end2 { if a[start1] < a[start2] { arr[k] = a[start1] start1 += 1 }else { arr[k] = a[start2] start2 += 1 } k += 1 } while start1 < end1 { arr[k] = a[start1] start1 += 1 k += 1 } while start2 < end2 { arr[k] = a[start2] start2 += 1 k += 1 } start += seg * 2 } // 此時arr是一個臨時排好序的序列,需要讓它與a進行交換,使之再次排序 let temp = arr arr = a a = temp seg += seg } return a // 由於每次都會將arr於a進行交換,因此這里應該返回a,而不是arr } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
遞歸法:(遞歸法容易理解,但是遞歸存在棧溢出的風險)
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } func merge(left: [Int], right: [Int]) -> [Int] { var arr = [Int]() var l = 0, r = 0 while l < left.count && r < right.count { if left[l] < right[r] { arr.append(left[l]) l += 1 }else { arr.append(right[r]) r += 1 } } while l < left.count { arr.append(left[l]) l += 1 } while r < right.count { arr.append(right[r]) r += 1 } return arr } let left = Array(list.prefix(upTo: list.count/2)) let right = Array(list.suffix(from: list.count/2)) return merge(left: sort(left), right: sort(right)) } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
六、快速排序
快速排序采用分治法策略來把一個序列分成較小和較大的2個子序列,然后遞歸的排序兩個子序列。
步驟如下:
- 挑選基准值,從一個序列中選出一個元素,這個元素稱為“基准值”
- 分割:重新排序列,所有比基准值小的元素,放在基准值前面。所有比基准值大的元素,方法基准值后面(與基准值相等的元素,可以放在任何一側)。在這個分割結束之后,對基准值的排序就已完成。
- 遞歸排序子序列:遞歸的將小於基准值的子序列和大於基准值的子序列排序。
平均時間復雜度:O(n*log n);最壞時間復雜度:O(n2);最好時間復雜度:O(n*log n);空間復雜度:O(n*log n);穩定性:不穩定(即:排序前兩個相同元素,在排序后前后順序發生了改變)
實現代碼(swift):
func sort(_ list: [Int]) ->[Int] { if list.count < 2 { return list } func swap(_ li: inout [Int], i: Int, j: Int) { guard i != j else { return } let temp = li[i] li[i] = li[j] li[j] = temp } func partition(_ arr: inout [Int], left: Int, right: Int) -> Int { let pivot = arr[left] var j = left + 1 var i = j while i <= right { if arr[i] < pivot { swap(&arr, i: i, j: j) j += 1 } i += 1 } swap(&arr, i: left, j: j - 1) return j - 1 } func partitionRandom(_ arr: inout [Int], left: Int, right: Int) ->Int { let pivotIndex = left + (right - left) / 2 // 每次取序列中間的數作為基准值,這里也可以采用其他算法計算基准值 swap(&arr, i: pivotIndex, j: left) // 將基准值放在當前序列左側的第一位 return partition(&arr, left: left, right: right) } func quickSort(_ arr: inout [Int], left: Int, right: Int) { if left < right { let pivot = partitionRandom(&arr, left: left, right: right) quickSort(&arr, left: left, right: pivot - 1) // 這里不包括基准值 quickSort(&arr, left: pivot + 1, right: right) // 這里不包括基准值 } } var li = list quickSort(&li, left: 0, right: list.count - 1) return li } let list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] let li = sort(list) print("排序結果:\(li)")
七、堆排序
是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆的性質:即子節點的鍵值或索引總是小於(或者大於)它的父節點。
通常堆是通過一維數組來實現的。在數組起始位置為0的情形中:
- 父節點i的左子節點在位置(2i+1)
- 父節點i的右子節點在位置(2i+2)
- 子節點i的父節點在位置((i-1)/2)
堆排序是基於完全二叉樹實現的,在將一個數組調整成一個堆的時候,關鍵之一的是確定最后一個非葉子節點的序號,即:最后一個父節點的位置。從上可知,一個子節點i的父節點位於(i-1)/2可知,(加入數組長度為n)最后一個子節點位於:n-1,因此最后一個父節點位於:(n-1-1)/2,即:n/2-1。
步驟如下:
- 初始化構建一個大頂堆(或小頂堆)。
- 將堆頂元素與數組中最后一個元素交換,這樣數組中最后的元素就是數組中最大(或最小)的。
- 然后除去最后一個元素,繼續構建大頂堆(或小頂堆),重復步驟2,這樣數組中后面的元素即為有序的,直到有序序列長度為n-1為止。
平均時間復雜度:O(n*log n);最壞時間復雜度:O(n*log n);最好時間復雜度:O(n*log n);空間復雜度:O(1);穩定性:不穩定(即:排序前兩個相同元素,在排序后前后順序發生了改變)
實現代碼(swift):
func sort(_ list: inout [Int]) { if list.count < 2 { return } // 交換元素 func swap(_ arr: inout [Int], i: Int, j: Int) { let temp = arr[i] arr[i] = arr[j] arr[j] = temp } // 構建大頂堆 func buildMaxHeap(_ arr: inout [Int], start: Int, end: Int) { var father = start // 父節點下標 var son = start * 2 + 1 // 左孩子 while son <= end { if son + 1 <= end, arr[son] < arr[son + 1] { // 選出最大的元素下標 son += 1 } if arr[father] > arr[son] { break // 跳出函數 } // 交換父子節點 swap(&arr, i: father, j: son) father = son son = father * 2 + 1 } } // 初始化堆,從最后一個父節點開始 for i in 0...list.count/2-1 { buildMaxHeap(&list, start: i, end: list.count-1) } // 將堆頂元素(最大或最小)與堆的最后一個葉子節點交換,然后剩余元素繼續構建大頂堆 var i = list.count - 1 while i > 0 { swap(&list, i: 0, j: i) // 數組中第0個元素即為堆頂元素,將堆頂與堆的最后一個葉子節點交換 buildMaxHeap(&list, start: 0, end: i-1) i -= 1 } } var list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] sort(&list) print("排序結果:\(list)")
八、計數排序
是一種穩定的線性時間排序算法。計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等於i的元素的個數。然后根據數組C來將A中的元素排到正確的位置。
步驟如下:
- 找出待排序的數組中最大和最小的元素,計算出計數容器的大小 k
- 統計數組中每個值為 i 的元素出現的次數,存入數組C的第 i 項
- 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加)
- 反向填充目標數組:將每個元素 i 放在新數組的第C[i]項,每放一個元素就將C[i]減去1
平均時間復雜度:O(n + k);最壞時間復雜度:O(n + k);最好時間復雜度:O(n + k);空間復雜度:O(n + k);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不改變)(n:為數組個數, k:為計數容器大小)
實現代碼(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } var arr = Array<Int>(repeating: 0, count: list.count) var max = list.first!, min = list.first! // 選出最大值與最小值 for i in list { if i > max { max = i } if i < min { min = i } } // 計算計數容器的大小 let k = max - min + 1 var c = Array<Int>(repeating: 0, count: k) // 計數容器,用於存儲list中每個元素出現的個數 // 開始計數 for i in list { c[i - min] += 1 } // 對所有計數累加 for i in 1..<k { c[i] = c[i] + c[i - 1] }// 反向填充目標數組 var i = list.count - 1 while i >= 0 { let a = list[i] arr[c[a - min] - 1] = a c[a - min] -= 1 i -= 1 } return arr } var list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] print("排序結果:\(sort(list))")
九、桶排序
是計數排序的升級版,工作的原理是將數組分到有限數量的桶里,每個桶再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。適用於被排序的數組內的數值是均勻分配的。
步驟如下:
- 設置一個定量的數組當作空桶子。
- 遍歷序列,並且把項目一個一個放到對應的桶子去。
- 對每個不是空的桶子進行排序。
- 從不是空的桶子里把項目再放回原來的序列中。
平均時間復雜度:O(n + k);最壞時間復雜度:O(n2);最好時間復雜度:O(n);空間復雜度:O(n + k);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不改變)(n:為數組個數, k:為桶的數量)
實現代碼(swift):
// bucketSize:每個桶的容量 func sort(_ list: [Int], bucketSize size: Int) -> [Int] { if list.count < 2 { return list } // 插入排序 func insertSort(_ arr: inout [Int]) { if arr.count < 2 { return } for i in 1..<arr.count { var j = i - 1 let k = arr[i] while j >= 0, arr[j] > k { arr[j + 1] = arr[j] j -= 1 } arr[j + 1] = k } } var max = list[0], min = list[0] // 計算序列中的最大值與最小值(用於計算所需桶的個數) for i in list { if i > max { max = i }else if i < min { min = i } } // 初始化桶 let bucketCount = (max - min) / size + 1 // 計算所需桶的個數 var buckets = Array<[Int]>(repeating: [], count: bucketCount) // 利用映射函數,將數組分到指定的桶中 for i in list { let idx = (i - min) / size buckets[idx].append(i) } // 分別對每個桶子的元素進行插入排序 for i in 0..<buckets.count { if buckets[i].count < 2 { continue } insertSort(&buckets[i]) } var arr = [Int]() // 從桶中取出元素,放到新數組中 for bucket in buckets { arr.append(contentsOf: bucket) } return arr } var list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] print("排序結果:\(sort(list, bucketSize: 10))")
十、基數排序
是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用於整數。
基數排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由鍵值的最右邊開始,而MSD則相反,由鍵值的最左邊開始。
操作步驟:
MSD:
- 計算序列中,子元素最的位數
- 從最高位開始遍歷數組中的每個元素,進行桶排序
- 此時不對桶中的元素進行收集,而是對桶中元素個數>1的再次遞歸基數排序
- 直到遍歷完所有位數,排序結束
LSD:
- 計算序列中,子元素最大的位數
- 從最低位開始遍歷數組中的每個元素,取出元素當前位的數子作為桶的索引,分別放入0-9的桶子中
- 取出桶子中的數放入臨時數組中
- 重復2、3,直到遍歷完每一位,返回臨時數組
平均時間復雜度:O(n * k);最壞時間復雜度:O(n * k);最好時間復雜度:O(n * k);空間復雜度:O(n + k);穩定性:穩定(即:排序前兩個相同元素,在排序后前后順序不改變)(n:為數組個數, k:為桶的數量)
MSD算法實現(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } // 計算最大位數 func maxDigit(_ arr: [Int]) -> Int { var max = arr[0] for i in arr { if i > max { max = i } } var digit = 1 while max / 10 > 0 { digit += 1 max = max / 10 } return digit } // 遞歸調用 func sort(_ arr: inout [Int], maxDigit: Int) { if maxDigit < 1 || arr.count < 2 { return } var buckets = Array<[Int]>(repeating: [], count: 10) let base = maxDigit - 1 == 0 ? 1 : (maxDigit - 1) * 10 for i in arr { buckets[i / base % 10].append(i) } for i in 0..<buckets.count { if buckets[i].count > 1 { sort(&buckets[i], maxDigit: maxDigit - 1) } } var j = 0 for bucket in buckets { for el in bucket { arr[j] = el j += 1 } } } var arr = list let maxDigit = maxDigit(list) sort(&arr, maxDigit: maxDigit) return arr } var list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] print("排序結果:\(sort(list))")
LSD算法實現(swift):
func sort(_ list: [Int]) -> [Int] { if list.count < 2 { return list } // 計算最大位數 func maxDigit(_ arr: [Int]) -> Int { var max = arr[0] for i in arr { if i > max { max = i } } var digit = 1 while max / 10 > 0 { digit += 1 max = max / 10 } return digit } var arr = list let maxDigit = maxDigit(list) for i in 0..<maxDigit { // 初始化桶,0-9 var buckets = Array<[Int]>(repeating: [], count: 10) let base = i == 0 ? 1 : i * 10 for el in arr { buckets[el / base % 10].append(el) } var j = 0 for bucket in buckets { for el in bucket { arr[j] = el j += 1 } } } return arr } var list = [2, 3, 19, 5, 10, 40, 6, 15, 6, 9, 18] print("排序結果:\(sort(list))")