js 算法 - 冒泡排序

756 阅读2分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

冒泡排序

冒泡排序大概是每个编程人员所学的第一个算法。冒泡排序很简单,我们将前一个数据与后一个数据做比较,如果前一个数据比后一个数据大(从小到大排序),就将前一个数据与后一个数据做交换,这个操作一直从第一个数据进行到最后一个数据的前一个数据,这个时候我们就完成了一次冒泡操作。为了方便理解,我们可以将数据划分为两个区间,一个是冒泡区间(需要进行冒泡操作的区间),另一个是有序区间,每次冒泡操作都从冒泡区间找到最大值并放入有序区间里,因此如果一个数组有 n 个数据,也即是说我们需要进行 n 次的冒泡操作才能完成排序。

如下例,假设一个数组里有5个数字,需要从小到大排序。

进行第一次冒泡操作,从冒泡区间找到最大值 5,并放入有序区间。

1629447028(1).jpg

进行第二次冒泡操作,从冒泡区间找到最大值 4,并放入有序区间。

1629447071(1).jpg

以此类推,继续进行冒泡操作,直到完成排序为止。每次冒泡得到的结果就像如下图一样,每次冒泡都能够获取一个排好序的数且放到有序区间里。

1629439902(1).jpg

冒泡排序代码

function bubbleSort(arr) {
  if (arr.length <= 1) {
    return arr
  }
  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
      }
    }
  }
  return arr
}

i 循环体是用来遍历冒泡操作次数,数组有多少个数据,就需要进行多少次冒泡操作。j 循环体里面的操作就是一次的冒泡操作,j循环体用来遍历数组中的每个数据,判断是否需要做交换操作。

代码最难理解的地方大概是 j 循环体里的 j < arr.length - i - 1arr.length - i 即数组长度减去冒泡次数,因为每次完成冒泡都会排好一个数据,下一次冒泡并不需要操作排好的数据,也就是说下一次冒泡所操作的数据量必定是上一次冒泡所操作的数据量减 1,每次的冒泡所操作的数据量就等于 arr.length - iarr.length - i - 1 后面为什么还要减去 1 呢?那是因为需要防止 if 判断里的 arr[j+1] 下标越界,而且冒泡操作遍历到最后一个数据时根本不需要和后面一个数据进行判断是否需要交换数据,因为后面已经没有数据了。

优化版冒泡排序

细心的人会发现一个问题,上例图中的冒泡排序其实在第三次冒泡后就已经完成了排序,下面的冒泡操作似乎不需要继续进行下去。

image.png

这里可以进行优化,在某一次冒泡操作中,当任何数据都不进行交换操作时,证明该序列已经排好序,不必再进行下一次冒泡操作。也就是说,上例在第三次冒泡后,第四次冒泡发现冒泡区间里没有发生过任何的数据交换情况,这个时候就已经排好序了。

优化版的冒泡排序

function bubbleSort(arr) {
  if (arr.length <= 1) {
    return arr
  }
  for (var i=0; i<arr.length; i++) {
    var flag = false
    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 = true
      }
    }
    if (!flag) {
      break
    }
  }
  return arr
}
原地排序稳定性最好时间复杂度最坏时间复杂度平均时间复杂度
冒泡排序稳定O(n)O(n²)O(n²)