冒泡排序之优化
冒泡排序属于一种典型的交换排序。
交换排序顾名思义就是通过元素的两两比较,判断是否符合要求,如过不符合就交换位置来达到排序的目的。冒泡排序名字的由来就是因为在交换过程中,类似水冒泡,小(大)的元素经过不断的交换由水底慢慢的浮到水的顶端。
冒泡排序的思想就是利用的比较交换,利用循环将第 i 小或者大的元素归位,归位操作利用的是对 n 个元素中相邻的两个进行比较,如果顺序正确就不交换,如果顺序错误就进行位置的交换。通过重复的循环访问数组,直到没有可以交换的元素,那么整个排序就已经完成了。
**常用的场景是数组排序:即遍历数组,对数组相邻元素进行对比,若想得到升序序列,则将大的元素放后边,小的放前边。若想得到降序数组则反过来**
图文讲解:
冒泡排序:从第一个数据开始,一次比较相邻元素的大小。如果前者大于后者,则进行交换操作。把大的元素往后交换。通过多轮迭代,直到没有交换操作为止。冒泡排序就像是在一个水池中处理数据一样,每次会把最大的那个数据传递到最后。
算法实现:
@Test
public void sortTest(){
int[] arr = {4,1,3,2,6,7,8,9,10,11,13,15};
int temp = -1;
int count = 0;
//需要进行arr.length-1次排序遍历
for (int i=0; i<arr.length; i ++){
//每次遍历,需要把所有无序的元素遍历完
for (int j=0 ; j<arr.length-1-i; j++){
//如果后边的元素比前边小则交换,反之则不变
if (arr[j+1] < arr[j]){
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
//记录循环执行的次数
count ++;
}
}
System.out.println("执行了" + count + "次");
System.out.println("----------------------------");
for (int i : arr) {
System.out.print(i + " ");
}
}
结果
执行了66次
----------------------------
1 2 3 4 6 7 8 9 10 11 13 15
优化一:
通过上面的图片,我们可以发现。在第三次排序的时候,数组其实已经是一个有序数组了。但循环依然在继续,这就白白的浪费了资源。如果我们能判断出数组是有序的,然后跳出循环,那就节省了一部分空间。
@Test
public void sortTest1(){
int[] arr = {4,1,3,2,6,7,8,9,10,11,13,15};
int temp = -1;
int count = 0;
for (int i=0; i<arr.length; i ++){
//设置一个布尔值,默认为true
boolean flag = true;
for (int j=0 ; j<arr.length-1-i; j++){
if (arr[j+1] < arr[j]){
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
//如果进行了交换,则把布尔值改为false
flag = false;
}
count ++;
}
//如果进行一次遍历,发现布尔值仍是true,则说明没有发生元素交换,
//即数组为有序数组,跳出循环即可
if (flag ){
break;
}
}
System.out.println("执行了" + count + "次");
System.out.println("----------------------------");
for (int i : arr) {
System.out.print(i + " ");
}
}
结果:
执行了30次
----------------------------
1 2 3 4 6 7 8 9 10 11 13 15
在以上优化的基础上,还存在一个问题,例如我们的初始数组如下:
int[] arr = {4,1,3,2,6,7,8,9,10,11,13,15};
我们发现arr在6之后的元素是有序的,根本不需要进行遍历和交换。然而,我们每次遍历是从0开始遍历到arr.length-i-1的。这就有可能包括了部分有序数组。
因此,如果我们能把无序数列和有序数组的边界标出来,每次遍历和交换的时候,只遍历到无序数组则也能省下部分空间。
@Test
public void sortTest2(){
int[] arr = {4,1,3,2,6,7,8,9,10,11,13,15};
int temp = -1;
int count = 0;
//定义最后一次交换位置时的索引
int lastChangeIndex = 0;
//初始化,第一次遍历数组时,需要遍历的长度
int sortBorder = arr.length-1;
for (int i=0; i<arr.length; i ++){
boolean flag = true;
for (int j=0 ; j< sortBorder; j++){
if (arr[j+1] < arr[j]){
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
//记录最后一次交换位置时的索引
lastChangeIndex = j;
flag = false;
}
count ++;
}
//将最后一次记录的索引,赋值给无序数列的边界,这就是下一次要遍历的数组边界
sortBorder = lastChangeIndex;
if (flag ){
break;
}
}
System.out.println("执行了" + count + "次");
System.out.println("----------------------------");
for (int i : arr) {
System.out.print(i + " ");
}
}
执行了14次
----------------------------
1 2 3 4 6 7 8 9 10 11 13 15
总结:
冒泡排序的思想其实很简单,就是相邻元素两两对比,判断是否需要交换顺序。但经过细心的考虑,有些特殊的情况,则可以提前确实数组局部有序,则不需再进行重复遍历。优化也就是把这些重复遍历的步骤给去除,最终得到高性能的冒泡排序算法。