冒泡排序是最简单的排序算法,因为很容易实现,并且也容易理解。基本思想就是每一趟迭代都把最大的元素移动到序列的最末尾(或者是把最小的元素移动到最前面),即数值大的元素像泡泡一样“冒”到了最后。
最近看到一篇博客上说冒泡排序在最佳情况下(序列已经是有序的)的时间复杂度为O(n),比快排和归并排序的O(nlog(n))还要快。我对此感到非常怀疑,因为按照最普通没有任何优化的写法,我觉得冒泡排序无论待排的序列是什么情况,其复杂度(按比较次数来算)都是O(n^2),因为有两层嵌套的循环。下面是Java的实现代码:
public static void bubbleSort(int[] a) {
for (int i = 0, len = a.length; i < len - 1; i++) {
for (int j = 0; j < len - 1; j++) {
if (a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
上面是我经常写的一种实现方法,很明显复杂度为T(n) = ((n-1)^2))即O(n^2)。今天看到了一篇博客上一个优化了一些的实现方式:
public static void bubbleSort(int[] a) {
for (int i = 0, len = a.length; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
注意内层循环的循环判断条件:j < len - 1 - i。可以这样优化的理由是,每一次内循环都会把未排序部分中的最大元素移动到它能到达的最末尾位置,举个例子来看看就明白了。
假设初始序列是[8,4,6,2,5,3],
i = 0: 经过内部循环后[4,6,2,5,3,8]
i = 1: 经过内部循环后[4,2,5,3,6,8]
i = 2: 经过内部循环后[2,4,3,5,6,8]
可以看到,当经过一次内部循环后,序列就会被分成未排序和已排序(加粗部分)两部分。内部循环其实只需要在未排序部分两两进行比较即可,没有必要再对已排序部分进行比较,这就是可以进行以上优化的原因。但是这样优化后复杂度虽然有所减小,但是仍然是指数形式的。T(n)=(1+(n-1))*(n-1) / 2 = n*(n-1) / 2仍然是O(n^2)。
后来我在stackoverflow上看到了有人提问(http://stackoverflow.com/questions/12505832/why-is-the-time-complexity-of-bubble-sorts-best-case-being-on?rq=1),要想在序列已有序的情况下使复杂度为O(n),其实只需要增加一个标记量,如果内部循环没有发生任何的交换(swap),则表示序列已经有序,此时可以跳出循环。
最终的优化代码如下:
public static void sort(int[] arr) {
// 标记内部循环是否发生了交换操作
boolean swapped = false;
for (int i = 0, len = arr.length; i < len - 1; i++) {
swapped = false;
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
swapped = true;
}
}
if (swapped == false)
break;
}
}