前言:各位小伙伴们,冒泡排序做为我入门编程第一个遇到的算法,对我来讲意义非凡。今天闲来从新拾起了这个算法,发现它居然还有这么大的优化空间,惊讶。那咱们就来优化一下它吧!写这篇文章呢主要是想和在座的各位小伙伴分享一下个人优化历程,二来还能够方便之后复习。废话很少说。咱们直接开始吧!java
基础比较好的小伙伴能够直接略过算法
我这么说是否是能明白一点呢?不明白也不要紧,就让咱们一块儿来看代码吧!编程
int[] array = {
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
复制代码
这是一个0-9的倒序排列的数组,咱们经过相邻元素的下标比对而后互换来完成从小到大的排序,如图:数组
这样咱们就完成了将9放到了数组的最后。完成了一次排序。bash
说到这里,那咱们如何用代码来实现呢?性能
int[] array = {
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
// 一次遍历,将相对最大的数放到数组底部
for (int j = 0; j < array.length - 1; j++) {
if (array[j] > array[j + 1]) {
int max = array[j];
array[j] = array[j + 1];
array[j + 1] = max;
}
}
复制代码
输出的结果: [8, 7, 6, 5, 4, 3, 2, 1, 0, 9]
测试
那就太好办了!再加一个循环吧!优化
int[] array = {
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
int max = 0;
// 一次遍历,在倒序状况下最少遍历的次数
for (int i = 0; i < arrays.length - 1; i++) {
// 二次遍历,将相对最大的数放到数组底部
for (int j = 0; j < array.length - 1; j++) {
if (array[j] > array[j + 1]) {
max = array[j];
array[j] = array[j + 1];
array[j + 1] = max;
}
}
}
复制代码
输出结果: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
spa
咱们的目的:3d
最有用的办法就是观察算法的有效遍历数和实际遍历数
那咱们就想办法先为程序减小一些循环吧!
我发如今执行二次遍历时,程序越运行到后面,所作的排序就越少,由于数组后面的元素都已排序完成,无需再进行循环判断
一次遍历的计数(i)就至关于咱们数组已经排好元素的个数
减去(i),就能够减小循环次数
int[] array = {
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
// 程序有效运行的次数
int runCount = 0;
// 一共遍历的次数
int allCount = 0;
int max = 0;
// 一次遍历,在倒序状况下最少遍历的次数
for (int i = 0; i < array.length - 1; i++) {
// 二次遍历,将相对最大的数放到数组底部
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
max = array[j];
array[j] = array[j + 1];
array[j + 1] = max;
runCount += 1;
}
allCount += 1;
}
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
复制代码
输出结果:
runCount = 45
allCount = 45
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码
可是这是在极端状况下(彻底倒序),咱们拿到的数组大多数状况都是无序散乱的。这样的优化明显不能知足咱们的要求。这又为第二次优化提供了思路...
每每散乱的数组实际所需的遍历次数是远小于极端状况(彻底倒序)的,然而咱们程序仍是会进行循环遍历.
那咱们不如作个判断,判断它是否须要进行实际遍历,若是不须要了。那数组确定是排序完成了!那咱们就能够跳出循环了。
int[] array = {
3, 6, 2, 7, 9, 5, 0, 1, 4, 8
};
// 程序有效运行的次数
int runCount = 0;
// 一共遍历的次数
int allCount = 0;
int max = 0;
// flag判断排序是否完成 true-完成;false-未完成
boolean flag;
// 一次遍历,在倒序状况下最少遍历的次数
for (int i = 0; i < array.length - 1; i++) {
// 每次循环重置flag为true
flag = true;
// 二次遍历,将相对最大的数放到数组底部
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
max = array[j];
array[j] = array[j + 1];
array[j + 1] = max;
runCount += 1;
// 进入循环表示数组未排序完成,需再次循环
flag = false;
}
allCount += 1;
}
// 若是已经完成排序,则跳出循环
if (flag) {
break;
}
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
复制代码
输出结果:
runCount = 22
allCount = 42
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码
未加判断输出结果:
runCount = 22
allCount = 45
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码
从结果能够看出来,比未加判断的实际遍历次数少了3次。
可是优化到此为止好像仍是缺了点什么,若是数组天生有一部分就是无需排序的,那咱们又会浪费不少次的循环,这么一想,第三步优化就有了方向。
如图:
后面的 5 6 7 8 9 原本就是排序完成的,那按照咱们的代码还要去对后面的代码进行循环遍历,那样是很不科学的!
我用几个变量来动态记录数组所需遍历的次数就能够解决问题了。
int[] array = {
3, 6, 2, 7, 9, 5, 0, 1, 4, 8
};
// 程序有效运行的次数
int runCount = 0;
// 一共遍历的次数
int allCount = 0;
int max = 0;
// flag判断排序是否完成 true-完成;false-未完成
boolean flag;
// 无序数组循环边界,默认为数组长度array.length - 1
int sortBorder = array.length - 1;
// 记录数组最后进行排序的位置
int lastChange = 0;
// 一次遍历,在倒序状况下最少遍历的次数
for (int i = 0; i < array.length - 1; i++) {
// 每次循环重置flag为true
flag = true;
// 二次遍历,将相对最大的数放到数组底部
for (int j = 0; j < sortBorder; j++) {
if (array[j] > array[j + 1]) {
max = array[j];
array[j] = array[j + 1];
array[j + 1] = max;
runCount += 1;
// 进入循环表示数组未排序完成,需再次循环
flag = false;
// 记录数组最后进行排序的位置
lastChange = j;
}
allCount += 1;
}
// 动态设置无序数组循环边界
sortBorder = lastChange;
// 若是已经完成排序,则跳出循环
if (flag) {
break;
}
}
System.out.println("runCount = " + runCount);
System.out.println("allCount = " + allCount);
System.out.println(Arrays.toString(array));
复制代码
输出结果:
runCount = 22
allCount = 35
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码
有的小伙伴会问了:怎么仍是多出来 13 次啊?
我以为在目前看来多于的次数对于有效遍历提供了必定的帮助,因此并非彻底无效的。
不懂的小伙伴能够复制代码进行 bebug 也能够直接问我。可是不要中止思考哦。说不定你就找出更好的优化方案了呢!