冒泡排序,即一个无序的数组中,经过处理的数组后面的元素比前一个大。
在这个过程中,数字会像水冒泡一样,从水下移动到水面上破裂,对应数字,就是最大的数字移动到序列的顶端,所以被称为冒泡排序。
int[] arr = new int[]{6, 2, 1, 3, 5, 4};
判断条件
arr[j] < arr[j + 1]
要做到这个,在程序中实现需要通过循环外加一个临时变量来处理
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
考虑到数组里的元素顺序发生变化,一般一次完整下来无法达到目的,数组 arr 一个完整循环排序下来的结果是
[2, 1, 3, 5, 4, 6]
显然1和2,5和4还不能满足,所以需要一个外层循环来处理这个问题
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
至此,一个简单的冒泡排序结束。
外层循环控制元素个数,内层循环负责处理数据比较并交换。
看一下哪里可以有优化的地方
从循环次数入手
发现外层循环总共30次。循环次数多意味着计算量大,消耗cpu高
减少外层循环次数
外层循环第一次排序后的数组依次为
[2, 6, 1, 3, 5, 4]
[2, 1, 6, 3, 5, 4]
[2, 1, 3, 6, 5, 4]
[2, 1, 3, 5, 6, 4]
[2, 1, 3, 5, 4, 6]
即6个元素遍历了5次,有一次不用循环
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
这次循环次数为25次,(30-25)/30*100≈17%,即提升了 17%。
鉴于第一次外层循环结束后,数组最后一个值是最大的,接下来下来只需要对前面的5个元素进行比较
内层循环在现有的基础上减少外层循环次数
在上面优化的基础上,在外层循环的基础上数组后面的大小都是已经固定的。接下来每次外循环下来内循环只需要对数组未进行正常排序的进行处理即可。
for (int i = 0; i < arr.length -1; i++) {
int len = arr.length - 1 -i;
for (int j = 0; j < len; j++) {
// 5 > 3
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
这次循环次数为15次,(30-15)/30*100=50%,即相对原始算法提升了 50%,相对于第一次优化后 (25-15)/25=40%,即提升了40%。
在外层循环外添加是否进行数据交换标识
因为前面的排序下来前面的已经是按照正常顺序排列的了,后面循环如果内层没有进行判断过的直接跳出循环,较少循环次数。
代码如下
boolean flag = true;
// 第一个执行完后最后一个数是最大的,所以外层循环不需要比较最后一个
for (int i = 0; i < arr.length - 1; i++) {
flag = false;
for (int j = 0; j < arr.length - 1; j++) {
// 前一个数大于后一个
if (arr[j] > arr[j + 1]) {
// 临时变量保存大的数
int temp = arr[j];
// 后一个数提前
arr[j] = arr[j + 1];
// 临时变量赋值到大的数
arr[j + 1] = temp;
flag = true;
}
}
if (!flag) {
break;
}
}
这次循环次数为12次,解决了重复循环的问题,(30-12)/30*100=60%,即相对原始算法提升了 40%,相对于第二次优化后 (15-12)/15=20%,即提升了60%。
个别案例不代表真实情况,极端情况下冒泡排序的时间复杂度为O(n^2)。
即数字全部倒序,要求正序排列的情况下。
完整代码如下
int[] arr = new int[]{6, 2, 1, 3, 5, 4};
int cycleTotal = 0;
int exchangeTotal = 0;
// 3、如果内部循环没有数据交换了,代表排序都完成了
boolean flag = true;
// 1、第一个执行完后最后一个数是最大的,所以外层循环不需要比较最后一个
for (int i = 0; i < arr.length - 1; i++) {
flag = false;
// 2、外层数组已经排序完成了,所以在确保最后一个数最大的基础上,减少外层循环次数
for (int j = 0; j < arr.length - 1 -i; j++) {
// 前一个数大于后一个
if (arr[j] > arr[j + 1]) {
// 临时变量保存大的数
int temp = arr[j];
// 后一个数提前
arr[j] = arr[j + 1];
// 临时变量赋值到大的数
arr[j + 1] = temp;
exchangeTotal++;
flag = true;
}
cycleTotal++;
System.out.println(Arrays.toString(arr));
System.out.println(cycleTotal);
}
if (!flag) {
break;
}
}
System.out.println("===========================");
System.out.println(Arrays.toString(arr));
System.out.println(exchangeTotal);
看了一下资料,上面的冒泡排序是非正宗的,非正宗的是从左到右排序,正宗冒泡排序是从右到左,代码如下
int[] arr = new int[]{6, 2, 1, 3, 5, 4};
int cycleTotal = 0;
int exchangeTotal = 0;
// 3、如果内部循环没有数据交换了,代表排序都完成了
boolean flag = true;
// 1、第一个执行完后最后一个数是最大的,所以外层循环不需要比较最后一个
for (int i = 0; i < arr.length -1; i++) {
// flag = false;
// 2、外层数组已经排序完成了,所以在确保最后一个数最大的基础上,减少外层循环次数
for (int j = arr.length - 1; j >= i + 1; j--) {
// 前一个数大于后一个
if (arr[j - 1] > arr[j]) {
// 临时变量保存大的数
int temp = arr[j - 1];
// 后一个数提前
arr[j -1] = arr[j];
// 临时变量赋值到大的数
arr[j] = temp;
exchangeTotal++;
// flag = true;
}
cycleTotal++;
System.out.println(Arrays.toString(arr));
System.out.println(cycleTotal);
}
if (!flag) {
break;
}
}
System.out.println("===========================");
System.out.println(Arrays.toString(arr));
System.out.println(exchangeTotal);
数据交换可以采用按位异或的方式来处理
arr[j] ^= arr[j+1];
arr[j+1] ^= arr[j];
arr[j] ^= arr[j+1];
参考链接
https://www.cnblogs.com/zs234/archive/2012/04/18/3233380.html
https://www.jianshu.com/p/8ac11052174b
https://zhuanlan.zhihu.com/p/631610992