一.什么是冒泡排序
1.不断比较两个相邻元素,将大的往后或者往前排,就像水里的泡泡一样,越往后越接近水面泡泡体积越大,由此得名.
用图来说明:

2.用java代码实现冒泡排序
// ============================= 第0版 开始 ======================
@Test
public void sort01() {
int []arr = {1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88};
System.out.println("原始数组:" + Arrays.toString(arr) + ",数组长度" +arr.length);
System.out.println("第0版 冒泡排序");
firstBubbleSort0(arr);
System.out.println("排序后数组" + Arrays.toString(arr));
}
/**
* 普通版 第0版冒泡
* @param array
*/
private void firstBubbleSort0(int[] array) {
int ifTimes = 0;
int swapTimes = 0;
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length -1; j++) {
// if次数
ifTimes ++;
if (array[j] > array[j+1]) {
int temp;
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
swapTimes ++ ;
}
}
System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array));
}
System.out.println("if判断次数ifTimes = " + ifTimes);
System.out.println("发生交换次数swapTimes = " + swapTimes);
}
// ============================= 第0版 结束 ======================
原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]数组长度11
第0版 冒泡排序
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第4次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第5次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第6次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 110
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
可以看出冒泡排序的空间复杂度为O(1),时间负责度为O(n^2)
3.对于每轮排出来的最后(大)元素,其实可以不参与接下来的排序
一起来看看代码实现(在内部循环中每次遍历终点 array.length - i,i为外层循环 )
// ============================= 第一版 开始 ======================
@Test
public void testUnSortArray() {
int []arr = {43, 22, 6, 5, 23, 2, 50, 65, 78, 88, 1};
System.out.println("原始数组:" + Arrays.toString(arr));
firstBubbleSort(arr);
System.out.println("排序后数组" + Arrays.toString(arr));
}
/**
* 普通版 第一版冒泡
* @param array
*/
private void firstBubbleSort(int[] array) {
int ifTimes = 0;
int swapTimes = 0;
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length -i -1; j++) {
// if次数
ifTimes ++;
if (array[j] > array[j+1]) {
int temp;
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
swapTimes ++ ;
}
}
System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array));
}
System.out.println("ifTimes = " + ifTimes);
System.out.println("swapTimes = " + swapTimes);
}
打印结果:
原始数组:[43, 22, 6, 5, 23, 2, 50, 65, 78, 88, 1]
第0次后结果为:[22, 6, 5, 23, 2, 43, 50, 65, 78, 1, 88]
第1次后结果为:[6, 5, 22, 2, 23, 43, 50, 65, 1, 78, 88]
第2次后结果为:[5, 6, 2, 22, 23, 43, 50, 1, 65, 78, 88]
第3次后结果为:[5, 2, 6, 22, 23, 43, 1, 50, 65, 78, 88]
第4次后结果为:[2, 5, 6, 22, 23, 1, 43, 50, 65, 78, 88]
第5次后结果为:[2, 5, 6, 22, 1, 23, 43, 50, 65, 78, 88]
第6次后结果为:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 55
发生交换次数swapTimes = 22
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
时间复杂度为O(n*(n-1)/2)
冒泡排序就算完成了,好了你已经完全掌握了冒泡排序.(如果你真这样想,那你就天真了,文章也应该结束了).
二.冒泡排序优化
1.如果原来的数组接近完全排序
假如数组:
1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88
当第一轮排序完成后,得到结果
1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88
2.如果还是使用原始的冒泡排序(第一版)
原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]
第一版 冒泡排序
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第4次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第5次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第6次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 55
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
我们发现第0次遍历后,数组已经是完全排序.后面的遍历其实没有必要.
3.来了来了,第二版代码
每次内部循环完之后,判断数组是否已经是有序状态
// ============================= 第二版 ======================
@Test
public void sort22() {
int []arr = {1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88};
System.out.println("原始数组:" + Arrays.toString(arr));
System.out.println("第二版 冒泡排序(如果数组已经有序,结束排序)");
sortArrayWithAllArrayIsSorted(arr);
System.out.println("排序后数组" + Arrays.toString(arr));
}
/**
* 对已经排序的 可以减少开销, 当数组已经是排序状态后 不会继续进行
* 判断目标是 整个 数组是否已经排序好
* @param array
*/
private void sortArrayWithAllArrayIsSorted(int[] array) {
int ifTimes = 0;
int swapTimes = 0;
for (int i = 0; i < array.length; i++) {
// 是否已经排序好了(是否发生互换 否定)
boolean isSorted = true;
for (int j = 0; j < array.length - 1 - i ; j++) {
// if次数
ifTimes ++;
if (array[j] > array[j+1]) {
int temp;
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSorted = false;
swapTimes ++ ;
}
}
System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);
// 如果没有互换 说明已经排序完成
if (isSorted) {
break;
}
}
System.out.println("if判断次数ifTimes = " + ifTimes);
System.out.println("发生交换次数swapTimes = " + swapTimes);
}
// ============================= 第二版 结束 ======================
3.1 使用该代码排序接近排序的数组
原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]
第二版 冒泡排序(如果数组已经有序,结束排序)
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 19
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
这种接近完全排序的数组,在这种情况下优势明显.
3.2 如果数组不是接近完全排序
使用第二版 冒泡排序,排序下列数组
原始数组:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第0次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 34
发生交换次数swapTimes = 3
[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
相比较于第一版,还是减少了很多遍历比较次数.
4.如果数组部分有序,只需要遍历无序部分.
观察3.2中例子, 数组中 22, 23, 43, 50, 65, 78, 88 部分不需要排序,那么遍历的时候能不能把这部分排除呢?
所以咱们第三版排序,在第二版的基础上考虑部分有序,遍历到无序 和 有序的分界处就可以了.
上菜:
@Test
public void sort32() {
int []arr = {2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88};
System.out.println("原始数组:" + Arrays.toString(arr));
System.out.println("第三版 冒泡排序(遍历到无序和有序的边界)");
sortArrayWithPartyArrayIsSorted(arr);
System.out.println("排序后数组" + Arrays.toString(arr));
}
/**
* 遍历到无序和有序的边界
* 判断目标是 部分(前半无序,后半有序 会减少if)
* @param array
*/
private void sortArrayWithPartyArrayIsSorted(int[] array) {
int ifTimes = 0;
int swapTimes = 0;
// 最后一次发生交换的地方
int lastSwapIndex = 0;
// 无序部分长度, 每次比较只需要比到这里为止
int unSortBorder = array.length -1;
for (int i = 0; i < array.length; i++) {
// 是否已经排序好了(是否发生互换 否定)
boolean isSorted = true;
for (int j = 0; j < unSortBorder; j++) {
// for次数
ifTimes ++;
if (array[j] > array[j+1]) {
int temp;
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSorted = false;
// 当本轮for完成 j 到 array.length 的位置元素已经排序,下轮不需要遍历
lastSwapIndex = j;
swapTimes ++ ;
}
}
System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);
// 如果没有互换 说明已经排序完成
// 更新边界
unSortBorder = lastSwapIndex;
if (isSorted) {
break;
}
}
System.out.println("if判断次数ifTimes = " + ifTimes);
System.out.println("发生交换次数swapTimes = " + swapTimes);
}
// ============================= 第三版 结束 ======================
原始数组:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第三版 冒泡排序(遍历到无序和有序的边界)
第0次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 13
发生交换次数swapTimes = 3
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
和例子3.2中同样的数组,减少了21次if判断.
5.刚刚我们只考虑了数组的一边是否有边界(有序无序边界),我们能不能考虑数组两边是否有边界呢
思路:i是偶数从左到右冒大泡, 如果i是基数 从右到左冒小泡.
// 第四版(考虑双向边界)
@Test
public void sort43() {
int []arr = {2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1};
sortArrayWithDoublePartyArrayIsSorted(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 对已经排序的 可以减少开销, 当数组已经是排序状态后 不会继续进行
* 判断目标是 双边界(从两边观察 是否有已经排好序的部分) 数组是否已经排序好
* @param array
*/
private void sortArrayWithDoublePartyArrayIsSorted(int[] array) {
int temp;
int ifTimes = 0;
int swapTimes = 0;
// 最后一次发生交换的地方
int lastLeftSwapIndex = 0;
int lastRightSwapIndex = array.length -1;
for (int i = 0; i < array.length; i++) {
// 是否已经排序好了(是否发生互换 否定)
boolean isSorted = true;
// 每次从第个开始判断if
int beginBorder = lastLeftSwapIndex;
// 无序部分长度, 每次比较只需要比到这里为止
int endBorder = lastRightSwapIndex;
if(i % 2 == 0) {
for (int j = beginBorder; j < endBorder; j++) {
// if次数
ifTimes ++;
//
if (array[j] > array[j+1]) {
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSorted = false;
// 当本轮for完成 j 到 array.lenght 的位置元素已经排序
lastRightSwapIndex = j;
// test 代码
swapTimes ++ ;
}
}
} else {
for (int j = endBorder - 1; j >= 0; j--) {
// if次数
ifTimes++;
//
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
isSorted = false;
// 当本轮for完成 endBorder - j 到 endBorder 的位置元素已排序排序
lastLeftSwapIndex = j;
// test 代码
swapTimes++;
}
}
}
System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);
// 如果没有互换 说明已经排序完成
if (isSorted) {
break;
}
}
System.out.println("if判断次数ifTimes = " + ifTimes);
System.out.println("发生交换次数swapTimes = " + swapTimes);
}
以数组:[ 2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1 ] 为例
第三版结果:
原始数组:[2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1]
第三版 冒泡排序(遍历到无序和有序的边界)
第0次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 78, 1, 88],isSorted=false
第1次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 1, 78, 88],isSorted=false
第2次后结果为:[2, 5, 6, 22, 23, 43, 50, 1, 65, 78, 88],isSorted=false
第3次后结果为:[2, 5, 6, 22, 23, 43, 1, 50, 65, 78, 88],isSorted=false
第4次后结果为:[2, 5, 6, 22, 23, 1, 43, 50, 65, 78, 88],isSorted=false
第5次后结果为:[2, 5, 6, 22, 1, 23, 43, 50, 65, 78, 88],isSorted=false
第6次后结果为:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第7次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第8次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 55
发生交换次数swapTimes = 10
第四版结果:
原始数组:[2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1]
第四版 冒泡排序(考虑双向边界)
第0次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 78, 1, 88],isSorted=false
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 28
发生交换次数swapTimes = 10
第四版结果明显优于第三版结果,但是第四版缺点就是代码量复杂.
git代码:https://github.com/foxiaotao/java_interview/tree/master/src/sort/bubble