你不知道的冒泡排序优化

846 阅读9分钟

  什么是冒泡排序呢?冒泡排序其实就是一种基础的交换排序。它之所以被叫作冒泡排序是因为需要排序的每一个元素都可以像汽水中的小气泡一样,根据自身的大小,经由交换慢慢“浮”到数组的一侧。

1. 算法思想

  以从左往右、从小到大为例
   (1) 从左往右依次比较相邻元素的大小,更大的元素交换到右侧。
   (2) 从第一组相邻元素比较到最后一组相邻元素,结束后最后一个元素则是参与比较的元素中的最大的元素。
  (3) 再重新从左往右依次比较相邻,但前一轮中得到的最后一个元素不参与比较,由此得到新一轮的最大元素。
  (4) 重复步骤(3),直到没有任何一组元素需要进行比较。
  为了更形象地理解冒泡排序,我通过动画的形式给大家展示一下冒泡算法是如何具体的移动的呢?

  有一个无序数组:3,44,38,5,47,15,36,26,27,希望从小到大排序。
  按照冒泡排序的思想,我们应该把相邻元素进行两两比较,根据其比较的大小来交换两者的位置,动画过程解释如下:
  首先让3和44进行比较,发现3比44小,因此元素位置不改变。
  接下来比较44和38,发现44比38大,交换两者的位置。

  继续让44和5比较,发现44比5大,交换两者的位置。

  继续让44和47比较,发现44比47小,不交换位置。
  接下来让47和15比较,发现47比15大,交换两者的位置。

  继续让47和36比较,发现47比36大,交换两者的位置。

  继续让47和26比较,发现47比26大,交换两者的位置。

  最后让47和27比较,发现47比27大,交换两者的位置。

  这样一来,元素47作为数组的最大元素,就像生活中的汽水里的小气泡"沉在底部"。
  这时候,冒泡排序的第一轮就结束了,数列最右侧的元素47就可以认为是一个有序的区域,而这个有序的区域目前只有一个元素。

  下面,让我们来进行第二轮排序。
  首先让3和38比较,发现3比38小,不交换位置。
  接下来让38和5比较,发现38比5大,交换两者位置。

  继续让38和44比较,发现38比44小,不交换位置。
  接下来让44和15比较,发现44比15大,交换两者位置。

  继续让44和36比较,发现44比36大,交换两者位置。

  继续让44和26比较,发现44比26大,交换两者位置。

  继续让44和27比较,发现44比27大,交换两者位置。

  第二轮排序结束后,右侧的有序区有了两个元素。

  依次类推,第三轮过后的结果为:

  第四轮过后的结果是:

  第五轮过后的结果是:

  第六轮过后的结果是:

  第七轮过后的结果是:

  第八轮过后的结果是:

  第九轮过后的结果是:

  由此,数组中所有元素都是有序的,这就是冒泡排序的整体思路。

2. 实现

  遥远的Arithmetic星球,植根于算法文明。在追问门派,有这么一对师徒正在探讨如何能够更好地实现冒泡排序:

  冒泡排序灯塔第一层:

var bubbleSort = function (arr) {
    console.time('冒泡排序耗时')
    for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arr.length - i - 1; j++) {
            // 比较相邻两个元素,进行交换
            if (arr[j] > arr[j+1]) {
                var temp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = temp
            }
            // console.log(arr)
        }
        // console.log(i, arr)
    }
    console.timeEnd('冒泡排序耗时')
    return arr
}

var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27]
console.log(bubbleSort(arr))

  冒泡排序灯塔第二层:

var bubbleSortFlag = function (arr) {
    console.time('冒泡排序耗时')
    for (var i = 0; i < arr.length; i++) {
        var flag = true // 设置每一轮是否进行了交换,默认未交换,即有序
        for (var j = 0; j < arr.length - i - 1; j++) {
            // 比较相邻两个元素,进行交换
            if (arr[j] > arr[j+1]) {
                var temp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = temp
                flag = false // 有元素交换,无序
            }
            // console.log(arr)
        }
        console.log(i, arr)
        if (flag) break;
    }
    console.timeEnd('冒泡排序耗时')
    return arr
}

var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27]
console.log(bubbleSortFlag(arr))

  有一个数组:3,5,2,4,1,6,7,8,9,该数组前半部分(3,5,2,4,1)是无序的,而后半部分(6,7,8,9)是有序的。其冒泡排序流程如下:
  第一轮:
  元素3和5比较,3比5小,不交换位置。
  元素5和2比较,5比2大,交换两者位置。

  元素5和4比较,5比4大,交换两者位置。

  元素5和1比较,5比1大,交换两者位置。

  元素5和6比较,5比6小,不交换位置。
  元素6和7比较,6比7小,不交换位置。
  元素7和8比较,7比8小,不交换位置。
  元素8和9比较,8比9小,不交换位置。
  第一轮结束,数组有序区包含一个元素。


  第二轮:
  元素3和2比较,3比2大,交换两者位置。

  元素3和4比较,3比4小,不交换位置。
  元素4和1比较,4比1大,交换两者位置。

  元素4和5比较,4比5小,不交换位置。
  元素5和6比较,5比6小,不交换位置。
  元素6和7比较,6比7小,不交换位置。
  元素7和8比较,7比8小,不交换位置。
  元素8和9比较,8比9小,不交换位置。
  第二轮结束,数组有序区包含两个元素。

  依次类推,第三轮结束,数组有序区包含三个元素。

  第四轮结束,数组有序区包含四个元素。

  第五轮结束,数组有序区包含五个元素。

  第六轮结束,数组有序区包含六个元素。

  第七轮结束,数组有序区包含七个元素。

  第八轮结束,数组有序区包含八个元素。

  第九轮结束,数组全部元素都属于有序区。

  冒泡排序灯塔第三层:

var bubbleSortDic = function (arr) {
    var len = arr.length - 1 
    var firstSwrapIndex = 0  // 设置最后交换元素的位置
    console.time('冒泡排序耗时')
    for (var i = 0; i < arr.length; i++) {
        var flag = true
        for (var j = 0; j < len; j++) {
            // 比较相邻两个元素,进行交换
            if (arr[j] > arr[j+1]) {
                var temp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = temp
                flag = false
                firstSwrapIndex = j
            }
            // console.log(arr)
        }
        len = firstSwrapIndex // 最后一次交换的位置
        console.log(i, arr, len)
        if (flag) break;
    }
    console.timeEnd('冒泡排序耗时')
    return arr
}

var arr = [3, 5, 2, 4, 1, 6, 7, 8, 9]
console.log(bubbleSortDic(arr))

3. 冒泡排序分析

4. 冒泡排序升级——鸡尾酒排序

  有一个数组:15,9,18,6,12,23,2,32 29,其鸡尾酒排序过程如下:
  第一轮,与冒泡排序一样操作,最后得到如下结果。



  第二轮开始不一样,它是从右往左进行比较和交换。
  首先元素29和23进行比较,发现29比23大,不交换位置。
  然后元素23和2进行比较,发现23比2大,不交换位置。
  接下来元素2和18进行比较,发现2比18小,交换两者位置。



  继续元素2和12进行比较,发现2比12小,交换两者位置。



  继续元素2和6进行比较,发现2比6小,交换两者位置。



  继续元素2和15进行比较,发现2比15小,交换两者位置。



  继续元素2和9进行比较,发现2比9小,交换两者位置。



  最终,第二轮结束后得到的结果如下:



  第三轮:
  首先比较元素9和15,9比15小,不交换位置。
  然后比较元素15和6,15比6大,交换两者位置。

  继续比较元素15和12,15比12小,交换两者位置。

  接着比较元素15和18,15比18小,不交换位置。
  然后比较元素18和23,18比23小,不交换位置。
  最后比较元素23和29,23比29小,不交换位置。
  第三轮结束后状态如下:

  以此类推,第四轮结束后的结果为:

  第五轮结束后的结果:

  第六轮结束后的结果:

  第七轮结束后的结果:

  第八轮结束后的结果:

  第九轮结束后的结果:

4. 鸡尾酒排序实现

  冒泡排序灯塔顶层第一关:

var cocktailSort = function (arr) {
    // 来去为一回合,共循环数组长度的一半
    // console.log(arr.length)
    var temp // 交换时暂存变量
    console.time('鸡尾酒排序耗时')
    for (var i = 0; i < arr.length / 2; i++) {
        // 从左向右依次比较并进行交换
        for (var j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                // 进行交换
                temp = arr[j] 
                arr[j] = arr[j+1]
                arr[j+1] = temp
            }
        }
        console.log(i, arr)
        // 从右向左依次比较进行交换
        for (var k = arr.length - 1 - i; k > i; k--){
            if (arr[k] < arr[k-1]) {
                temp = arr[k]
                arr[k] = arr[k-1]
                arr[k-1] = temp
            }
        }
        console.log(i, arr)
    }
    console.timeEnd('鸡尾酒排序耗时')
    return arr
}

var arr = [15, 9, 18, 6, 12, 23, 2, 32, 29]
console.log(cocktailSort(arr))

  冒泡排序灯塔顶层第二关:

var cocktailRanking = function (arr) {
    // 来去为一回合,共循环数组长度的一半
    // console.log(arr.length)
    var temp // 交换时暂存变量
    var isSorted // 标记是否进行交换
    console.time('鸡尾酒排序耗时')
    for (var i = 0; i < arr.length / 2; i++) {
        // 从左向右依次比较并进行交换
        isSorted = true
        for (var j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                // 进行交换
                temp = arr[j] 
                arr[j] = arr[j+1]
                arr[j+1] = temp
                isSorted = false
            }
        }
        console.log(i,arr)
        if (isSorted) break
        // 标记未进行交换
        isSorted = true
        // 从右向左依次比较进行交换
        for (var k = arr.length - 1 - i; k > i; k--){
            if (arr[k] < arr[k-1]) {
                temp = arr[k]
                arr[k] = arr[k-1]
                arr[k-1] = temp
                isSorted = false
            }
        }
        console.log(i,arr)
        if (isSorted) break
    }
    console.timeEnd('鸡尾酒排序耗时')
    return arr
}

var arr = [15, 9, 18, 6, 12, 23, 2, 32, 29]
console.log(cocktailRanking(arr))

  冒泡排序灯塔顶层终极:

var cocktailSort = function (arr) {
    // 来去为一回合,共循环数组长度的一半
    // console.log(arr.length)
    var temp // 交换时暂存变量
    var leftBorder = 0, rightBorder = arr.length - 1 // 设置左右边界值
    var leftSortIndex, rightSortIndex //设置左边最后一组交换位置的下标,右边最后一组交换位置的下标
    var isSorted // 标记是否进行交换
    console.time('鸡尾酒排序耗时')
    for (var i = 0; i < arr.length / 2; i++) {
        // 从左向右依次比较并进行交换
        isSorted = true
        for (var j = leftBorder; j < rightBorder; j++) {
            if (arr[j] > arr[j+1]) {
                // 进行交换
                temp = arr[j] 
                arr[j] = arr[j+1]
                arr[j+1] = temp
                isSorted = false
                rightSortIndex = j
            }
        }
        rightBorder = rightSortIndex
        console.log(i,arr)
        if (isSorted) break
        // 标记未进行交换
        isSorted = true
        // 从右向左依次比较进行交换
        for (var k = rightBorder; k > leftBorder; k--){
            if (arr[k] < arr[k-1]) {
                temp = arr[k]
                arr[k] = arr[k-1]
                arr[k-1] = temp
                isSorted = false
                leftSortIndex = k
            }
        }
        leftBorder = leftSortIndex
        console.log(i,arr)
        if (isSorted) break
    }
    console.timeEnd('鸡尾酒排序耗时')
    return arr
}

var arr = [15, 9, 18, 6, 12, 23, 2, 32, 29]
console.log(cocktailSort(arr))

  冒泡排序算法就到此为止。
  其实排序算法博大精深,还有许多排序算法没来及总结,等我整理好后,我一定还会回来的。

如果喜欢我的文章请 "点赞" "评论" "关注",大家的支持就是我坚持下去的动力!若是以上内容有任何错误或者不准确的地方,欢迎留言指出,若你有更好的想法,也欢迎一起交流学习!