1. 基本思想
冒泡排序的基本思想:
对于待排序数列, 依次比较相邻元素, 若顺序相反, 则交换位置, 重复此步骤直至完成排序.
2. 过程演示
以 [5, 4, 3, 2, 1]
来演示整个排序过程
原始序列: 5, 4, 3, 2, 1
第一趟排序: 4, 5, 3, 2, 1
4, 3, 5, 2, 1
4, 3, 2, 5, 1
4, 3, 2, 1, 5
第二趟排序: 3, 4, 2, 1, 5
3, 2, 4, 1, 5
3, 2, 1, 4, 5
第三趟排序: 2, 3, 1, 4, 5
2, 1, 3, 4, 5
第四趟排序: 1, 2, 3, 4, 5
可以看出:
每一趟排序都将所经过的最大的元素放在了最后的位置, 所以第 n趟排序并不需要关心倒数第 n + 1个元素.
3. 代码实现
// 冒泡排序
private static int[] bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean swapped = false; // 第 (i + 1)次是否发生了元素交换
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j , j + 1);
printArr(j, j + 1, arr); // 打印比较过程
swapped = true;
}
}
if (!swapped) // 未发生元素交换的话, 表示数组已经是有序的了
break;
}
return arr;
}
/*
output:
原始数组: [5, 4, 3, 2, 1]
i = 0, j = 1 ---> [4, 5, 3, 2, 1]
i = 1, j = 2 ---> [4, 3, 5, 2, 1]
i = 2, j = 3 ---> [4, 3, 2, 5, 1]
i = 3, j = 4 ---> [4, 3, 2, 1, 5]
i = 0, j = 1 ---> [3, 4, 2, 1, 5]
i = 1, j = 2 ---> [3, 2, 4, 1, 5]
i = 2, j = 3 ---> [3, 2, 1, 4, 5]
i = 0, j = 1 ---> [2, 3, 1, 4, 5]
i = 1, j = 2 ---> [2, 1, 3, 4, 5]
i = 0, j = 1 ---> [1, 2, 3, 4, 5]
排序后: [1, 2, 3, 4, 5]
*/
// 交换数组中指定索引处的元素位置
private static void swap(int[] arr, int i, int j) {
if (i > arr.length - 1 || i < 0 || j > arr.length - 1 || j < 0) {
throw new RuntimeException("索引值有误");
}
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 打印数组元素
private static void printArr(int[] arr) {
System.out.println(Arrays.toString(arr));
}
// 打印 i, j的索引, 和当前数组的顺序
private static void printArr(int i, int j, int[] arr) {
System.out.print("i = " + i + ", j = " + j + " ---> ");
printArr(arr);
}
4. 复杂度分析
假设: 给定一个含有 n个元素的序列
最好情形:
给定序列是正序, 那么走一趟即可完成排序, 所需比较次数C和记录移动次数M均达到最小值, 即:
Cmin = n - 1
Mmin = 0
所以, 冒泡排序最好的时间复杂度是 O(n)
最坏情形:
给定序列是倒序, 那么需要进行 n - 1
趟排序, 第 i
趟排序需要进行 n - i
次比较, 且每次比较都必须移动记录三次来达到交换位置的效果, 在这种情况下, 比较和移动次数均达到最大值.
所以, 冒泡排序最坏的时间复杂度是 O(n^2)
Cmax = n * (n - 1) / 2 = O(n^2)
Mmax = 3 * n * (n - 1) / 2 = O(n^2)
综上所述, 冒泡排序的平均时间复杂度是 O(n^2)