一、概念
冒泡排序(Bubble Sorting):通过对待排序序列从前往后,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素主键从前移向后部,就像水底下冒泡一样逐渐往上冒。
通俗来讲就是:相邻元素对比,左边比右边大的话就换个位置,每轮对比下来就能确定一个最大值,第一轮最大值在最后位置,第二轮倒数第二大的值在倒数第二个位置上,以此类推,也就是需要对比的次数为数组长度-1,因为每次对比都能确定一个最大值,且放到最后。
二、图解
比如要冒泡如下数组:[3, 9, -1, 10, -2]
第一趟排序:
[3,-1,9,-2,10]
第二趟排序:
[-1,3,-2,9,10]
第三趟排序:
[-1,-2,3,9,10]
第四趟排序:
[-2,-1,3,9,10]
三、coding
1、lowb演示版
package com.chentongwei.struct.sort;
import java.util.Arrays;
/**
* Description:
*
* @author TongWei.Chen 2019-12-30 17:18:26
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, -2};
int temp = 0;
// 第一趟排序就是将第一大的数排在倒数第一位
for (int j = 0; j < arr.length - 1 - 0; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第一次排序后的数组");
System.out.println(Arrays.toString(arr));
// 第二趟排序就是将第二大的数排在倒数第二位
for (int j = 0; j < arr.length - 1 - 1; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第二次排序后的数组");
System.out.println(Arrays.toString(arr));
// 第三趟排序就是将第二大的数排在倒数第二位
for (int j = 0; j < arr.length - 1 - 2; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第三次排序后的数组");
System.out.println(Arrays.toString(arr));
// 第四趟排序就是将第二大的数排在倒数第二位
for (int j = 0; j < arr.length - 1 - 3; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第四次排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
每次排序都进行多-1,因为上面说了,每次排序都能确定一个最大的数且放到最后,所以每次都会多-1进行循环,一次比一次循环的少。
第一次排序后的数组
[3, -1, 9, -2, 10]
第二次排序后的数组
[-1, 3, -2, 9, 10]
第三次排序后的数组
[-1, -2, 3, 9, 10]
第四次排序后的数组
[-2, -1, 3, 9, 10]
2、优化版
上面lowb演示版就是为了演示了过程,很明显都是重复代码,只有循环的条件不一样,每次循环都少循环一次,多-1而已。所以循环每次都可以理解成-i(i是递减的),这就很明显了,再套个循环就好了。然后内层循环每次都arr.length - 1 - i
package com.chentongwei.struct.sort;
import java.util.Arrays;
/**
* Description:
*
* @author TongWei.Chen 2019-12-30 17:18:26
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, -2};
bubbleSort(arr);
}
// 将前面的冒泡排序算法封装成一个方法
public static void bubbleSort(int[] arr) {
int temp;
// 时间复杂度n2
for (int i = 0; i < arr.length - 1; i ++) {
for (int 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;
}
}
System.out.println("第" + (i + 1) + "次排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
}
时间复杂度分析:
最好、最坏、平均时间复杂度均为O(n2)第一次排序后的数组
[3, -1, 9, -2, 10]
第二次排序后的数组
[-1, 3, -2, 9, 10]
第三次排序后的数组
[-1, -2, 3, 9, 10]
第四次排序后的数组
[-2, -1, 3, 9, 10]
四、问题
如果我数组是完全有序的呢?或者说部分有序,比如排序两次就能得到结果了,但是我这套程序不管你有序无序,都直接排序arr.length-1次。这明显不够最优,比如我们有如下数组:[1, 2, 3, 4, 5],这不太需要每次都排序,排序一次直接拿到结果就行了。
思路:
可以定义一个变量flag,代表这次循环是否需要排序交换位置了,若没有,则代表已经排完了,直接break结束循环即可。具体实现如下:
package com.chentongwei.struct.sort;
import java.util.Arrays;
import java.util.Date;
/**
* Description:
*
* @author TongWei.Chen 2019-12-30 17:18:26
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
bubbleSort(arr);
}
// 将前面的冒泡排序算法封装成一个方法
public static void bubbleSort(int[] arr) {
int temp;
// 标识变量,表示是否进行过交换,默认没进行过交换
boolean flag = false;
// 时间复杂度n2
for (int i = 0; i < arr.length - 1; i ++) {
for (int j = 0; j < arr.length - 1 - i; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第" + (i + 1) + "次排序后的数组");
System.out.println(Arrays.toString(arr));
// 代表再排序中,一次交换都没发生过,也就是已经排好序了。
if (! flag) {
break;
}
// 重置flag,进行下次判断
flag = false;
}
}
}
第1次排序后的数组
[1, 2, 3, 4, 5]可以发现结果只输出了一次而已。最好时间复杂度为O(n),最坏时间复杂度为O(n2),但是最好的情况极难出现,所以平均时间复杂度还是O(n2)
五、测试效率
我们测试下10w条数据进行冒泡排序需要耗时多久。
package com.chentongwei.struct.sort;
import java.util.Arrays;
/**
* Description:
*
* @author TongWei.Chen 2019-12-30 17:18:26
*/
public class BubbleSort {
public static void main(String[] args) {
// 测试冒泡排序的效率,假设给10w个数据测试
int[] arr = new int[100000];
for (int i = 0; i < arr.length; i ++) {
// 生成【0,8000000】之间的一个随机数
arr[i] = (int)(Math.random() * 8000000);
}
long startTime = System.currentTimeMillis();
bubbleSort(arr);
long endTime = System.currentTimeMillis();
System.out.println("冒泡10w个数据所需要的时间是:" + (endTime - startTime) / 1000);
}
// 将前面的冒泡排序算法封装成一个方法
public static void bubbleSort(int[] arr) {
int temp;
// 标识变量,表示是否进行过交换,默认没进行过交换
boolean flag = false;
// 时间复杂度n2
for (int i = 0; i < arr.length - 1; i ++) {
for (int j = 0; j < arr.length - 1 - i; j ++) {
// 如果前面的数比后面的数大就交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// 代表再排序中,一次交换都没发生过,也就是已经排好序了。
if (! flag) {
break;
}
// 重置flag,进行下次判断
flag = false;
}
}
}
冒泡10w个数据所需要的时间是:23
可以看到是23s排序完10w条数据。这只是八大排序其中之一,后面每种排序算法我们都对比一下,看看差距到底有多大。