常用排序算法

前言

之前對(duì)排序算法理解不是很深刻,也容易把幾個(gè)算法混在一起,所以整理了幾個(gè)常用的排序算法,并嘗試在自己的理解上給幾個(gè)算法優(yōu)化了下。

冒泡排序

冒泡應(yīng)該是最好理解的排序算法,所以把它放在第一個(gè)講。冒泡的思想是:兩兩比較相鄰記錄,步驟為:
1、從第一個(gè)元素開始往后遍歷數(shù)組,并與每個(gè)元素對(duì)比,如果比其他元素小則交換元素
2、從第二個(gè)元素開始往后遍歷數(shù)組,并與每個(gè)元素對(duì)比,如果比其他元素小則交換元素
3、......

用代碼實(shí)現(xiàn)為:

func bubbleSort(var arr:[Int]) -> [Int]{
    for i in 0..<arr.count {
        for var j = i+1; j < arr.count; j++ {
            if arr[i] > arr[j] {
                let guide = arr[j]
                arr[j] = arr[i]
                arr[i] = guide
            }
        }
    }
    return arr
}

這是最傳統(tǒng)的冒泡排序,需要執(zhí)行的步驟為 n + (n-1) + (n-2) + (n-3) + ......+ 1= (n+1)*n/2,所以時(shí)間復(fù)雜度為O(n^2)。

傳統(tǒng)的冒泡排序只是暴力遍歷數(shù)組,逐個(gè)比較大小,只要比當(dāng)前值小的就會(huì)交換,將當(dāng)前位置排好的過程中,對(duì)于接下來的排序沒有任何的幫助,所以需要做一些改善。

如果在遍歷的時(shí)候從后往前遍歷,然后將較小的數(shù)往前面排,這樣的話在接下來的排列過程中,就會(huì)更有優(yōu)勢,因?yàn)槟阍诎旬?dāng)前位置排列好的同時(shí)將某些相對(duì)小的元素也排到了前面,在數(shù)據(jù)較多的時(shí)候能大大減少數(shù)據(jù)兩兩交換的次數(shù)。
口頭描述比較抽象,還是看代碼吧

func betterBulleSort(var arr:[Int]) -> [Int]{
    for i in 0..<arr.count {
        //從后往前遍歷,這里是與傳遞冒泡不一樣的地方
        for var j = arr.count - 1; j>0 && j>=i; j-- {
            //如果前面的元素比當(dāng)前的大,就把當(dāng)前元素往前移
            if arr[j-1] > arr[j] {
                let guide = arr[j-1]
                arr[j-1] = arr[j]
                arr[j] = guide
            }
        }
    }
    return arr
}

除此之外,如果待排序的數(shù)組為

2,1,3,4,5,6,7,8

在第一次1和2交換位置之后整個(gè)數(shù)組就是有序的了,接下來的所有比較都是浪費(fèi)的。這種情況下可以設(shè)置一個(gè)flag,增加一個(gè)flag為真才繼續(xù)遍歷的控制,代碼實(shí)現(xiàn)為:

func bestBubbleSort(var arr:[Int]) -> [Int]{
    var flag = true
    for i in 0..<arr.count {
        //如果flag為假則說明接下來的每個(gè)數(shù)都是有序的
        if !flag { break }

        for var j = arr.count - 1; j>0 && j>=i; j-- {
            //每次遍歷都默認(rèn)flag為false
            flag = false
            if arr[j-1] > arr[j] {
                let guide = arr[j-1]
                arr[j-1] = arr[j]
                arr[j] = guide
                //當(dāng)有發(fā)生位置交換,設(shè)置flag為真,表明接下來的數(shù)可能是無序
                flag = true
            }
        }
    }
    
    return arr
}

簡單排序

簡單排序的核心思想:找準(zhǔn)時(shí)機(jī)、再做替換
和冒泡相比,冒泡每次都做替換,簡單排序是遍歷數(shù)組后找到最小元素的下標(biāo)后再做替換。

func simpleSort(var arr:[Int]) -> [Int] {
    var minIndex = 0
    for i in 0..<arr.count {
        minIndex = i
        //遍歷找到最小元素所在的下標(biāo)
        for var j = i+1; j<arr.count; j++ {
            if arr[j]<arr[minIndex] { minIndex = j }
        }
        //如果最小元素下標(biāo)不是當(dāng)前下標(biāo)就交換
        if minIndex != i {
            let guide = arr[i]
            arr[i] = arr[minIndex]
            arr[minIndex] = guide
        }
    }
    
    return arr
}

簡單排序的時(shí)間復(fù)雜度也是O(N^2)
雖然簡單排序和冒泡排序的時(shí)間復(fù)雜度一樣,但是簡單排序在性能方面還是好很多的,交換次數(shù)沒像冒泡那么頻繁。

直接插入排序

直接插入排序的基本操作是將一個(gè)無序的數(shù)插入到有序的表中,從而得到一個(gè)有序的表。
步驟:
1、將需要插入的數(shù)取出賦值給哨兵。
2、向前遍歷表,每個(gè)元素都與哨兵比較大小,如果比哨兵大,則將這個(gè)元素向后移動(dòng)一個(gè)位置,知道找到比哨兵小的元素,將哨兵放入該位置。
看看代碼實(shí)現(xiàn):

func directInsertSort(var arr:[Int]) -> [Int] {
    var guide = 0
    for i in 1..<arr.count {
        //如果當(dāng)前元素比前面元素大說明現(xiàn)在為有序狀態(tài)
        if arr[i-1] < arr[i] { continue }
        
        //取出需要排序的元素
        guide = arr[i]
        //向前遍歷表,每個(gè)元素與哨兵比較大小
        for var j=i-1; j>=0 && arr[j]>guide; j-- {
            //比哨兵大的元素向后移動(dòng)
            arr[j+1] = arr[j]  
        }
        //將哨兵插入合適位置
        arr[j+1] = guide
    }

    return arr
}

直接插入排序的時(shí)間復(fù)雜度為O(n^2)

希爾排序

希爾排序(Shell Sort)可以看成是對(duì)直接插入排序的優(yōu)化,相對(duì)于直插法,它比較的步長變長了,基本思路為,將一個(gè)以一定的步長分割為多個(gè)組,每個(gè)組分別使用直插法進(jìn)行排序,之后縮短步長,繼續(xù)這個(gè)操作,直到步長為1為止。希爾排序使得排序算法的時(shí)間復(fù)雜度突破了O(n^2)的限制。

假設(shè)有這樣一組數(shù)

[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]

共16個(gè)元素如果我們以步長為5開始進(jìn)行排序
先將這組數(shù)一五為步長,分為幾組,第一組為[a0,a5,a10,a15],第二組為[a2,a6,a11],第三組為......,分完組應(yīng)該是這樣的:

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

然后用直插法對(duì)每列進(jìn)行排序,得到:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

經(jīng)過第一次排序得到的數(shù)組為:

[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]

可以看出第一個(gè)元素10已經(jīng)在正確的位置上了,接下來已3為步長再次對(duì)數(shù)組進(jìn)行分組:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后再以步長為1進(jìn)行排序,這里就是直接插入排序了

代碼實(shí)現(xiàn)為:

func shell(var arr:[Int]) -> [Int] {
    //指定步長
    var gap = arr.count
    var temp = 0
    var j = 0
    
    do{
        gap /= 2
        
        for i in gap..<arr.count
         {
            if arr[i] > arr[i-gap] { continue }
            
            temp = arr[i]
            for j = i-gap; j>0 && arr[j]>temp;j-=gap {
               arr[j+gap] = arr[j]
            }
            arr[j+gap] = temp
        }
    }while(gap>1)
    
    return arr
}

希爾算法的步長是決定時(shí)間復(fù)雜度的關(guān)鍵,但是究竟應(yīng)該選擇什么樣的步長才是最好的,目前還是一個(gè)數(shù)學(xué)難題。不過大量的研究數(shù)據(jù)表明,步長與時(shí)間復(fù)雜度存在以下關(guān)系:

步長與時(shí)間復(fù)雜度關(guān)系

快速排序

快速排序應(yīng)該是排序算法中最經(jīng)典的了,最早是由圖靈獎(jiǎng)的獲得者Tony Hoare設(shè)計(jì)出來的。它與冒泡排序比較相似,也是通過不斷比較得出有序的表,但是相對(duì)于冒泡,快排中增大了記錄的比較和移動(dòng)距離,將關(guān)鍵字較大的記錄直接移動(dòng)到后面,關(guān)鍵字較小的記錄從后面移動(dòng)到前面,從而大大減少了總的比較次數(shù)和移動(dòng)交換次數(shù)。

快排的基本思想是:通過一趟排序?qū)⒋判虻谋矸譃閮刹糠?,即比關(guān)鍵字小的部分和比關(guān)鍵字大的部分,然后再對(duì)這兩個(gè)部分重復(fù)操作,直到整個(gè)序列有序

文字描述難免有些抽象,來看看代碼怎么實(shí)現(xiàn)吧,在看代碼過程中請(qǐng)記住快排序的基本思想,會(huì)有助于理解

func quickSort(var arr:[Int], var low:Int, var high:Int) -> [Int]{
    
    //遞歸退出條件
    if low < high {
        /*!
         *1、獲取flag值的下標(biāo)和已經(jīng)初略排序好的數(shù)組:
         *flag前(后)面的元素比flag值?。ù螅?,
         */
        let pivot = partition(arr, low: low, high: high).0
        arr = partition(arr, low: low, high: high).1
        /*!
         *2、遞歸,flag前后兩部分繼續(xù)此操作,直到有序?yàn)橹?         */
        arr = quickSort(arr, low: low, high: pivot-1)
        arr = quickSort(arr, low: pivot+1, high: high)
    }
    
    return arr
}

func partition(var arr:[Int], var low:Int, var high:Int) -> (Int,[Int]) {
    var flag = arr[low]
    var guide:Int?
    /*!
     *兩頭開工,遍歷數(shù)組,
     *將比key大的移動(dòng)到后面,
     *比key小的移動(dòng)到前面
     */
    while (low < high){
        //往前遍歷
        while low<high && arr[high]>flag { high-- }
        arr = swapeObject(arr, low: low, high: high)
        //往后遍歷
        while low<high && arr[low]<flag{ low++ }
        arr = swapeObject(arr, low: low, high: high)
    }
    
    return ((arr as NSArray).indexOfObject(flag),arr)
}

//MARK: 交互元素
func swapeObject(var arr:[Int], low:Int, high:Int) -> [Int]{
    let guide = arr[high]
    arr[high] = arr[low]
    arr[low] = guide
    
    return arr
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 其實(shí)寫排序算法的博客已經(jīng)有很多了,其中不乏某些細(xì)心的博主去仔細(xì)講解各種排序的過程,甚至有使用gif圖來表現(xiàn)排序過程...
    qufl閱讀 2,592評(píng)論 0 51
  • //聯(lián)系人:石虎QQ: 1224614774昵稱:嗡嘛呢叭咪哄 //常用的排序算法 細(xì)節(jié)請(qǐng)看:http://blo...
    石虎132閱讀 463評(píng)論 0 4
  • 本文僅作為個(gè)人學(xué)習(xí)排序算法的參考筆記,若想詳細(xì)了解學(xué)習(xí),請(qǐng)前往 http://blog.csdn.net/han_...
    biubiu15閱讀 636評(píng)論 0 0
  • 當(dāng)我們進(jìn)行數(shù)據(jù)處理的時(shí)候,往往需要對(duì)數(shù)據(jù)進(jìn)行查找操作,一個(gè)有序的數(shù)據(jù)集往往能夠在高效的查找算法下快速得到結(jié)果。所以...
    Single_YAM閱讀 1,162評(píng)論 0 3
  • 一、常見排序算法一覽: 時(shí)間復(fù)雜度: 是一個(gè)函數(shù),它定量描述了該算法的運(yùn)行時(shí)間。 空間復(fù)雜度:一個(gè)算法在運(yùn)行過程中...
    夕望有你閱讀 993評(píng)論 0 0

友情鏈接更多精彩內(nèi)容