iOS冒泡算法優化

前言

關於數組排序的問題,在以前的文章有很詳細的介紹(連接:《iOS面試之道》算法基礎學習(下))。在這篇文章中,筆者會對經典的冒泡算法進行優化。先來看下未經優化的冒泡算法:面試

冒泡算法

//經典版
func bubbleSort( _ array: inout [Int]) -> [Int]? {
   //i表示數組中依次比較的總次數
  for i in 0..<array.count {
       for j in 0..<array.count - i - 1) {
         //j表示下標,判斷第j位和j+1位元素大小,進行排序
          if array[j] > array[j+1] {
              //元素左右進行交換
              let tmp = array[j]
              array[j] = array[j+1]
              array[j+1] = tmp
           }                
       }
   }
    return array
}
複製代碼

咱們都知道傳統的冒泡算法是經過2個for循環來比較相鄰元素的值,每次一輪大的循環結束,都會固定一個值,而後對已固定數值外的元素接着遍歷,最終使得數組有序。這樣的寫法看上去是沒有問題,可是時間複雜度卻很是高達到了O(n^2),因此說這個算法不是一個「優秀」的算法。算法

解決方法

怎麼才能優化這個算法呢,咱們仍是經過個例子來看下。假設如今有一個數組array = [7,6,1,2,3,4],不難看出這個數組其實經歷過2次循環就已經使得數組有序了,可是程序仍是會繼續進行無心義的比較。那麼這時咱們能夠經過一個Bool值,來肯定當前數組是否已經有序,如有序則中斷循環,直接看代碼:數組

//進化版
func bubbleSort( _ array: inout [Int]) -> [Int]? {
    for i in 0..<array.count {
        //設置bool判斷是否有序
        var isSorted = true     
         for j in 0..<array.count - (i+1) {      
            if array[j] > array[j+1] {
                let tmp = array[j]
                array[j] = array[j+1]
                array[j+1] = tmp
                //有交換 數組依舊無序
                isSorted = false  
            }     
        }
        if(isSorted) {
            //有序 中斷循環
            break
        }
    }
    return array
}
複製代碼

解決方法簡單粗暴,每次外層循環開始isSorted爲true,若元素未進行位置交換,則證實數組已有序,結束循環。學習

那這個算法還有繼續優化的空間麼,答案是確定的。用另外一個例子來解釋一下,假設如今數組array = [1,3,2,4,5,6]。按照上面的寫法咱們的確能夠在3輪判斷後就結束循環,很是高效。但問題是後面的4,5,6元素已經有序,每次的循環仍是會對後三個元素進行判斷。優化

解決方法

問題的所在是由於咱們對已排序好的數組進行了不少無心義的判斷,因此須要咱們對已經排好序的元素進行邊界限定,來減小判斷,看下代碼:spa

//超級進化版
func bubbleSort( _ array: inout [Int]) -> [Int]? {
    //記錄數組邊界
    var arrayBorder = array.count - 1
    for i in 0..<array.count {
        //設置bool判斷是否有序
        var isSorted = true
         for j in 0..<arrayBorder {
            if array[j] > array[j+1] {
                let tmp = array[j]
                array[j] = array[j+1]
                array[j+1] = tmp
                //有交換 數組依舊無序
                isSorted = false
                //記錄最後一次交換的位置
                arrayBorder = j
            }  
        }
        if(isSorted) {
            //有序 中斷循環
            break
        }
    }
    return array
}
複製代碼

若是說上面進化版的方法是減小外層i循環的無用判斷,那麼添加數組邊界,就是減小內部j循環的無用判斷。經過記錄須要交換位置的邊界值,來避免沒必要要的判斷。code

通過了2個版本的進化,冒泡排序已經實現了大蛻變,減小了不少沒必要要的操做。可是此時的冒泡排序還不是那麼的優秀,好比數組array = [2,3,4,5,6,1],能夠看出除了元素1之外,其餘元素都已經完成排序。可是把該數組帶入上面超級進化版本,你會發現它仍是進行了5輪的判斷才完成任務。排序

解決方法

出現上述問題的主要緣由仍是循環一直是從左至右進行,每次的起始都是從第0位開始。若是說能讓循環從右至左再進行一輪循環,就能很快的把元素1放到首位。這就要介紹一種新的排序方法--雞尾酒排序。get

雞尾酒排序:也就是定向冒泡排序, 雞尾酒攪拌排序, 攪拌排序 (也能夠視做選擇排序的一種變形), 漣漪排序, 來回排序 or 快樂小時排序, 是冒泡排序的一種變形。此演算法與冒泡排序的不一樣處在於排序時是以雙向在序列中進行排序。for循環

知道了解決方法,直接來看下最終的究極進化版本:

//究極進化版
func cocktailSort( _ array: inout [Int]) -> [Int]? {
    //數組左邊界
    var arrayLeftBorder = 0
    //數組右邊界
    var arrayRightBorder = array.count - 1
    for i in 0..<array.count/2 {
        //設置bool判斷是否有序
        var isSorted = true
        //第一輪循環 左->右
        for j in arrayLeftBorder..<arrayRightBorder {  
            if array[j] > array[j+1] { 
                let tmp = array[j]
                array[j] = array[j+1]
                array[j+1] = tmp
                //有交換 數組依舊無序
                isSorted = false
                //記錄最後一次交換的位置
                arrayRightBorder = j  
            }
        }
        
        if(isSorted) {
            //有序 中斷循環
            break
        }

        //再次初始化isSorted位true
        isSorted = true
        //第二輪循環 右->左
      for j in (arrayLeftBorder+1...arrayRightBorder).reversed() {
            if array[j] < array[j-1] {
                let tmp = array[j]
                array[j] = array[j-1]
                array[j-1] = tmp
                //有交換 數組依舊無序
                isSorted = false
                //記錄最後一次交換的位置
                arrayLeftBorder = j
            }
        }
        
        if(isSorted) {
            //有序 中斷循環
            break
        }
    }
    return array
}
複製代碼

雞尾酒排序將以前完整的一輪循環拆分爲從左->右和從右->左兩個子循環,這就保證了排序的雙向進行,效率較單項循環來講更高。 與此同時在這兩個循環中加入了以前兩個版本的特性isSorted有序邊界,使得排序更加高效。

總結

隨着對冒泡排序的不斷升入理解,發現了實際排序中的問題,經過將幾種方法的組合使用,使得改進後的冒泡排序更加高效,當數據量很是巨大時效率提高很是明顯。

相關文章
相關標籤/搜索