算法學習:經常使用排序方法

排序是咱們的平常開發中常常會遇到的需求,例如,在商品的列表頁面,咱們能夠根據各類維度(銷量、價格、人氣等)對商品的展現順序進行改變。算法

因此,對各個排序的性能的瞭解也是基礎且重要的。咱們先對排序這一塊進行一個總體的把握。數組

  • 冒泡排序
  • 選擇排序
  • 插入排序
  • 希爾排序
  • 堆排序
  • 歸併排序
  • 快速排序

關於排序的具體過程,本文沒有利用圖示來具體說明,可是在代碼中對每一步執行加上了一些 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 個元素的次小值。如此反覆執行,便能獲得一個有序序列了。

基本步驟

  1. 將無序序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆。
  2. 將堆頂元素與末尾元素交換,將最大元素"沉"到數組末端。
  3. 從新調整結構,使其知足堆定義,而後繼續交換堆頂元素與當前末尾元素,反覆執行調整及交換步驟,直到整個序列有序。

代碼實現

// 構建小頂堆的層次遍歷序列
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)使用分治法策略。

選擇一個基準數,經過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的全部數據都比另一部分的全部數據都要小。而後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。

快速排序流程:

  1. 從數列中挑出一個基準值。
  2. 將全部比基準值小的擺放在基準前面,全部比基準值大的擺在基準的後面(相同的數能夠到任一邊);在這個分區退出以後,該基準就處於數列的中間位置。
  3. 遞歸地把「基準值前面的子數列」和「基準值後面的子數列」進行排序。

代碼實現

/// 將數組以第一個值爲準分爲兩部分,前半部分比該值要小,後半部分比該值要大
///
/// - 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排序用法

在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)
相關文章
相關標籤/搜索