Swift 排序演算法

Hsusue發表於2018-12-05

本文采用陣列實現。

排序演算法 時間複雜度 空間複雜度 是否穩定
直接插入排序 O(n^2) O(1)
希爾排序 O(nlogn) O(1)
氣泡排序 O(n^2) O(1)
選擇排序 O(n^2) O(1)
歸併排序 O(nlogn) O(n)
快速排序 O(nlogn) O(logn)
堆排序 O(nlogn) O(1)

直接插入排序

思想:每次將無序區的第一個記錄按關鍵字插入到有序區的合適位置,並將有序區的長度加1。

func insertSort(array: inout [Int]) -> Void {
    guard array.count > 1 else {
        return
    }
    
    for i in 0..<(array.count-1) {
        if array[i + 1] < array[i] {// 小於有序區最大的數值,需要插入有序序列
            let temp = array[i + 1] // 儲存將要被有序區向後移頂替的值(即本輪插進有序區的值)
            var j = i + 1// 有序區的序號
            repeat {// 資料移動,比temp大的都往後移
                j -= 1
                array[j + 1] = array[j]
            } while (j - 1 >= 0) && (array[j - 1] > temp) // 還需要移動
            
            // 插入
            array[j] = temp
        }
    }
}
複製程式碼

希爾排序

又稱作縮小增量排序,是對直接插入排序的改進。

思路:shell排序是相當於把一個陣列中的所有元素分成幾部分來排序;先把幾個小部分的元素排序好,讓元素大概有個順序,最後再全面使用插入排序。一般最後一次排序都是和上面的直接插入排序一樣的。

/// 一趟希爾排序,增量為dk
func shellInsert(array: inout [Int], dk: Int) -> Void {
    for i in 0..<(array.count-dk) {
        if array[i + dk] < array[i] {// 小於有序區最大的數值,需要插入有序序列
            let temp = array[i + dk]// 儲存將要被有序區向後移頂替的值
            var j = i + dk// 有序區的序號
            repeat {// 資料移動,比temp大的都往後移
                j -= dk
                array[j + dk] = array[j]
            }while (j - dk >= 0) && (array[j - dk] > temp)// 還需要移動
            
            // 插入
            array[j] = temp
        }
    }
}

/// 希爾排序
func shellSort(array: inout [Int], dk: [Int]) -> Void {
    guard array.count > 1 else {
        return
    }

    // 按增量序列d[]對陣列array作希爾排序
    for dkItem in dk {
        shellInsert(array: &array, dk: dkItem)
    }
}
複製程式碼

氣泡排序

思路:它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素已經排序完成。

func bubbleSort(array:inout [Int]) -> Void {
    guard array.count > 1 else {
        return
    }

    for i in 0..<(array.count - 1) { // 外層迴圈為 陣列長度 - 1
        for j in 0..<(array.count - 1 - i) { // 內層迴圈為 外層迴圈 - i
            if array[j] > array[j + 1] { // 冒泡操作
                array.swapAt(j, j + 1)
            }
        }
    }
}
複製程式碼

選擇排序

思路:每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到全部待排序的資料元素排完。 選擇排序是不穩定的排序方法。

func selectSort(array:inout [Int]) -> Void {
    guard array.count > 1 else {
        return
    }
    
    for i in 0..<(array.count - 1) {
        var min = i // 有序區的最後一個位置
        
        for j in (i + 1)...(array.count - 1) { // 注意邊界, 是遍歷到最後一個
            if array[min] > array[j] {
                min = j; // 找到無序區中最小值的下標
            }
        }
        
        if i != min { // 防止相同位置交換操作
            array.swapAt(min, i)
        }
    }
}
複製程式碼

歸併排序

歸併是指將兩個或兩個以上的有序序列組合成一個新的有序序列。歸併排序是指把無序的待排序序列遞迴分解成若干個長度大致相等的有序自序列,並把有序子序列合併為整體有序序列的過程。

思路:

image.png

/// 合併
func merge(array:inout [Int], low:Int, mid:Int, high:Int){
    
    var tempArray = Array<Int>()
    var indexA = low
    var indexB = mid
    
    while (indexA < mid) && (indexB < high) {
        if array[indexA] < array[indexB]{
            tempArray.append(array[indexA])
            indexA += 1
        }else{
            tempArray.append(array[indexB])
            indexB += 1
        }
    }
    
    while indexA < mid {
        tempArray.append(array[indexA])
        indexA += 1
    }
    
    while indexB < high{
        tempArray.append(array[indexB])
        indexB += 1
    }
    
    var index = 0
    for item in tempArray{
        array[low + index] = item
        index += 1
    }
}
/// 拆分最後合併
func mergeSort(array:inout [Int], low: Int, high: Int) -> Void {
    guard array.count > 1 else {
        return
    }

    if low + 1 < high {
        
        let mid = (low + high) / 2
        mergeSort(array: &array, low: low, high: mid)
        mergeSort(array: &array, low: mid, high: high)
        merge(array: &array, low: low, mid: mid, high: high)
    }
}
// 測試用例
// var sortArray = [49, 38, 65, 97, 76, 13, 27, 49, 55, 04]
// mergeSort(array: &sortArray, low: 0, high: sortArray.count)
複製程式碼

快速排序

對氣泡排序的改進,常簡稱為快排。

思想:首先從待排序列中選定一個記錄,稱之為樞軸,通過關鍵字與樞軸的比較將待排序的序列劃分成位於樞軸前後的兩個自序列,其中樞軸之前的子序列的所有關鍵字都不大於樞軸,樞軸之後的子序列的所有關鍵字都不小於樞軸;此時樞軸已到位,再按同樣方法對這兩個子序列分別遞迴進行快速排序,最終使得整個序列有序。

Swift 排序演算法

// 進行一次劃分,並返回樞軸的最終位置
func partiton(array:inout [Int], low: Int, high: Int) -> Int {
    
    var left = low, right = high  // 設定左右起點
    let x = array[low] // 設定樞軸
    
    repeat{// left 和 right 從待排序列的兩端交替地向中間移動
        
        while (array[right] > x) && (left < right) { // 從右往左找, 找出比樞軸小的數
            right -= 1
        }
        
        while (array[left] <= x) && (left < right) { // 從左往左找, 找出比樞軸大的數
            left += 1
        }
        
        if left < right {
            array.swapAt(left, right) // 交換操作
        }
        
    } while left < right
    
    // 樞軸移到正確位置
    if low != right { // 防止交換位置相同
        array.swapAt(low, right) // 將樞軸和左邊有序區的的最後一個數交換,此時為right的位置
    }
    
    return left // 返回樞軸的最終位置
}
func quickSort (array:inout [Int], low: Int, high: Int) -> Void {
    guard array.count > 1 else {
        return
    }
    
    if low < high {// 長度大於1
        let pivot = partiton(array: &array, low: low, high: high)
        quickSort(array: &array, low: low, high: pivot - 1)// 對樞軸之前的子序列遞迴快排
        quickSort(array: &array, low: pivot + 1, high: high)// 對樞軸之後的子序列遞迴快排
    }
}
// 測試用例
// var sortArray = [49, 38, 65, 97, 76, 13, 27, 49, 55, 04]
// quickSort(array: &sortArray, low: 0, high: sortArray.count-1)
複製程式碼

堆排序

思路:首先將待排序列建成一個大頂堆,使得堆頂結點最大;將堆頂結點與堆尾結點交換位置,堆長度減1(即最大記錄排序到位);然後調整剩餘結點為堆,得到次大值結點;重複這一過程,即可得到一個升序序列。

Swift 排序演算法

Swift 排序演算法

/// 堆排序
// 篩選堆
func shiftDown(array:inout Array<Int>,i:Int,length:Int) -> Void {
    var i = i;
    let temp = array[i];// 儲存當前元素
    var k = 2*i + 1 // 因為陣列根結點為0,所以左子結點編號要 +1
    
    while k < length {// 從左子結點開始
        if((k+1 < length) && (array[k] < array[k+1])){//如果左子結點小於右子結點,k指向右子結點
            k += 1;
        }
        if(array[k] > temp) {//如果子節點大於父節點,將子節點值賦給父節點(不用進行交換)
            array[i] = array[k];
            i = k;
        } else {
            break;
        }
        k = k*2 + 1
    }
    
    array[i] = temp;// 將temp值放到最終的位置
}
func makeHeap(array:inout Array<Int>) -> Void {
    for i in (0...(array.count/2-1)).reversed() {
        //從第一個非葉子結點從下至上,從右至左調整結構
        shiftDown(array: &array, i: i, length: array.count)
    }
}
func heapSort(array:inout Array<Int>) -> Void {
    guard array.count > 1 else {
        return
    }
    
    // 構建大頂堆
    makeHeap(array: &array)
    
    // 調整堆篩選 + 交換堆頂元素與末尾元素
    for j in (1...(array.count-1)).reversed() {
        array.swapAt(0, j)//將堆頂元素與末尾元素進行交換
        shiftDown(array: &array, i: 0, length: j)//重新對堆篩選
    }
}
複製程式碼

相關文章