目录
- 概念
- 举例
- 原理
- 第一版代码实现
- 第一版缺点
- 第一版优化
- 第二版代码实现
- 第二版缺点
- 第二版优化
- 第三版代码实现
- 🌟个人总结
- 最后
概念
冒泡排序是一种基础的交换排序。像汽水中的气泡一样向上浮动。
举例
5 | 8 | 6 | 3 | 9 | 2 | 1 | 7 |
将8个数字组成的数列按照从小到大顺序排序。
原理
相邻元素两两比较,元素大的放到右边,值相等不交换。
过程如下:
每一轮都会遍历所有元素,进行两两比较,所以说一共需要遍历7轮(元素数量-1),平均时间复杂度是O(n2)。
第一版代码实现
import java.util.Arrays;
public class bubbleSort {
public static void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - i - 1; j++) {
int tmp = 0;
if (array[j] > array[j + 1]) {
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
System.out.println("第"+(i+1)+"轮"+Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = new int[]{5,8,6,3,9,2,1,7};
sort(array);
System.out.println("最后排序结果是"+Arrays.toString(array));
}
}
运行结果:
第一版缺点
可以发现,第六轮之后已经有序,代码却还是又进行了第七轮。若需要排序的序列为{3,4,2,1,4,5,6,7,8},那么按照以上版本
可以看出,第3轮已经排好序,程序却又做了4轮的无用功。
第一版优化
这种情况下,如果可以判断出数列已经有序,并作出标记,就可以不必执行剩下几轮排序,提前结束工作。
第二版代码实现
import java.util.Arrays;
public class bubbleSort {
public static void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//有序标记,每一轮初始值都是true
boolean isSorted = true;
for (int j = 0; j < array.length - i - 1; j++) {
int tmp = 0;
if (array[j] > array[j + 1]) {
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
//本轮有元素交换,所以不是有序数列,标记为false,还需要下一轮判断
isSorted = false;
}
}
//若本轮没有元素交换,跳出循环
if(isSorted){
break;
}
System.out.println("第"+(i+1)+"轮"+Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = new int[]{3,4,2,1,5,6,7,8};
sort(array);
System.out.println("最后排序结果是"+Arrays.toString(array));
}
}
这一版增加了一个布尔变量isSorted作为标记,若在本轮中有元素交换,则说明数列无序,继续执行下一轮排序;若没有元素交换,则说明数列已经有序,直接跳出大循环。
运行结果:
第二版缺点
看一下每一轮中两两元素交换的过程
图中可以发现,其实5,6,7,8这四个数字已经是有序的了,程序却又进行两两比较,这个过程其实duck不必。
第二版优化
可以在每一轮排序后,记录下来最后一次元素交换的位置,该位置就是无序数列的边界,再往后就是有序区了。
第三版代码实现
import java.util.Arrays;
public class bubbleSort {
public static void sort(int[] array) {
//记录最后一次交换的位置
int lastExchangeIndex = 0;
//无序数列的边界,每次比较只需要比到这里为止
int sortBoder = array.length - 1;
for (int i = 0; i < array.length - 1; i++) {
//有序标记,每一轮初始值都是true
boolean isSorted = true;
for (int j = 0; j < sortBoder; j++) {
int tmp = 0;
if (array[j] > array[j + 1]) {
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
//本轮有元素交换,所以不是有序数列,标记为false,还需要下一轮判断
isSorted = false;
//更新为最后一次交换元素的位置
lastExchangeIndex = j;
}
}
sortBoder = lastExchangeIndex;
//若本轮没有元素交换,跳出循环
if(isSorted){
break;
}
System.out.println("第"+(i+1)+"轮"+Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = new int[]{3,4,2,1,5,6,7,8};
sort(array);
System.out.println("最后排序结果是"+Arrays.toString(array));
}
}
运行结果:
每一轮交换详情
这一版的代码中,sortBorder是无序数列的边界。在每一轮排序过程中,处于sortBorder之后的代码就不需要进行比较。这就是
第一轮中5,6,7,8不需要比较;
第二轮中4,5,6,7,8不需要比较;
第三轮中,3,4,5,6,7,8不需要比较
🌟个人总结
- 冒泡排序思想:
相邻元素两两比,不符要求换位置。
一共比较length轮,每轮比较length次。
- 冒泡排序复杂度:
O(n2)—O(n)—O(n2)—O(1)[平均—最好—最坏—空间复杂度] - 冒泡排序特点:稳定
- 冒泡排序代码框架:
定义最后交换的位置
定义每一轮需要交换的元素的边界
for(int i = 0;i<数组长度;i++){ //外循环控制排序的轮次
假设数组已经是有序的了,接下来看本轮是否有元素交换
for(int j = 0;j < 边界;j++){ //内循环控制每一轮中元素的交换
如果左边大于右边{
两数交换
记录交换的最后位置
因为有元素交换了,所以本轮数组是无序的
}
}
标记边界
本轮没有元素交换,说明数组已经有序,跳出外循环
}
- 根据框架默写一遍完整代码:
新建文件:Sort.java
import java.util.Arrays;
public class Sort {
public static void bubbleSort(int[] arr) {
int lastExchangeIndex = 0; //最后交换位置
int sortBorder = arr.length - 1; //无序数列边界
for(int i = 0;i < arr.length - 1;i++){ //外循环控制轮次
boolean isSorted = true; //有序标记
for(int j = 0;j < sortBorder;j++){ //内循环控制元素交换次数
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
lastExchangeIndex = j;
isSorted = false;
}
}
sortBorder = lastExchangeIndex;
if(isSorted)
break;
}
}
public static void main(String[] args) {
int[] arr = {3,4,2,1,5,6,7,8};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
nace!
最后
其实这并不是最优的,还有一种排序算法是基于冒泡排序的一种升级排序法:鸡尾酒排序。