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元素已经有序,每次的循环还是会对后三个元素进行判断。

解决方法

问题的所在是因为我们对已排序好的数组进行了很多无意义的判断,所以需要我们对已经排好序的元素进行边界限定,来减少判断,看下代码:

//超级进化版
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循环的无用判断。通过记录需要交换位置的边界值,来避免不必要的判断。

经过了2个版本的进化,冒泡排序已经实现了大蜕变,减少了很多不必要的操作。但是此时的冒泡排序还不是那么的优秀。我们可以再来看一个例子,比如数组array = [2,3,4,5,6,1],可以看出除了元素“1”以外,其他元素都已经完成排序。但是把该数组带入上面超级进化版本,你会发现它还是进行了5轮的判断才完成任务。

解决方法

出现上述问题的主要原因还是循环一直是从左至右进行,每次的起始都是从第0位开始。如果说能让循环从右至左再进行一轮循环,就能很快的把元素1放到首位。这就要介绍一种新的排序方法--鸡尾酒排序。

鸡尾酒排序:也就是定向冒泡排序, 鸡尾酒搅拌排序, 搅拌排序 (也可以视作选择排序的一种变形), 涟漪排序, 来回排序 or 快乐小时排序, 是冒泡排序的一种变形。此演算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

知道了解决方法,直接来看下最终的究极进化版本:

//究极进化版
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有序边界,使得排序更加高效。

总结

随着对冒泡排序的不断升入理解,发现了实际排序中的问题,通过将几种方法的组合使用,使得改进后的冒泡排序更加高效,当数据量非常巨大时效率提升非常明显。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容

  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 主题:房地产分析 1 交易之初的分析工作都为交易的成败奠定了基调,许多惨重的失败都源于交易初期分析工作的疏漏。 2...
    晴晴爱颖颖阅读 130评论 0 3
  • 我还年轻 我的生命乐章不过刚刚开始 一切都还来得及 花儿正在盛放 裙角翩翩在风中起舞 红红的太阳照的大地一片暖洋洋...
    咖啡豆小姐啊阅读 218评论 0 1