思路
对列表的无序部分从头至尾扫描一遍,扫描过程中通过不断交换相邻两个元素,最终把最大(小)元素交换到列表末尾。
- 首先比较第一和第二个数,把较大的放在第二位。
- 比较第二个和第三个数,再把较大的数放在第三位。
… … - 如此比较下去,直到比较完最后两个数。这样就把整个列表中最大的数交换到了末尾。
- 上面三步只是完成了对列表无序部分(一开始认为整个列表都是无序的)的一趟扫描,接下来我们将对列表剩下的无序部分按照上三步重复操作,每扫描一次都会把无序部分的最大值交换到排序部分的末尾,最终就会把所有元素按从小到大的顺序排列。
案例推演
排序无序数组 [10,1,35,61,89,36,55],案例来源于此博客。
- 第一趟排序
第一次比较:10 和1 比较,10大于1 ,两者交换位置,把10放1后面。 [1,10,35,61,89,36,55]
第二次比较:10和35比较,10小于35,两者不交换位置。 [1,10,35,61,89,36,55]
第三次比较:35和61比较,35小于61,两者不交换位置。 [1,10,35,61,89,36,55]
第四次比较:61和89比较,61小于89,两者不交换位置。[1,10,35,61,89,36,55]
第五次比较:89和36比较,89大于36,两者交换位置。 [1,10,35,61,36,89,55]
第六次比较:89和55比较,89大于55,两者交换位置。[1,10,35,61,36,55,89]
经过第一趟6次比较后成功将无序部分的最大元素89交换到了数组末尾。 - 第二趟排序
接下来对剩下的6个无序元素按照上述比较规则重新排序,把它们当中的最大值61放到无序部分末尾(即数组倒数第二位)。
… …
依照此法排序,经过6趟排序后就可将此数组排序完成。下图展示了每扫描一遍数组无序部分后数组的整体情况:
结合整个排序过程,可以得到: 若 数 组 待 排 序 部 分 元 素 个 数 为 N , 则 将 其 排 序 完 毕 需 要 扫 描 N − 1 次 。 第 k 次 扫 描 时 需 要 比 较 相 邻 元 素 N − k 次 。 \color{red}若数组待排序部分元素个数为N,则将其排序完毕需要扫描N-1次。第k次扫描时需要比较相邻元素N-k次。 若数组待排序部分元素个数为N,则将其排序完毕需要扫描N−1次。第k次扫描时需要比较相邻元素N−k次。
代码实现
实现代码选择Scala语言。外层for循环控制扫描次数(N-1次),其取值范围为:[0,N-1),内层循环控制每次扫描时元素比较次数(N-k次),其取值范围为:[0,N-k)。
def bubbleSort(array: Array[Int]): Unit = {
// i 的取值范围是 [0,length-1)
for (i <- 0 until array.length - 1) {
// j取值范围是:[0, length - (i + 1)),
// i + 1就是上文中的扫描次数k,这里i是从0开始计数,而k是从1开始计数,两者相差1
for (j <- 0 until array.length - (i + 1)) {
// 比较相邻元素,大的放后面
if (array(j) > array(j + 1)) {
val temp = array(j)
array(j) = array(j + 1)
array(j + 1) = temp
}
}
}
}
时间复杂度
排序一个有n个元素的无序数组,最坏情况下需要比较: (n-1)+(n-2)+…+1 = (n-1) *(n-1+1) / 2 次,所以时间复杂度为O(n^2)。