冒泡排序
算法思想
冒泡排序属于一种典型的交换排序。
交换排序顾名思义就是通过元素的两两比较,判断是否符合要求,如过不符合就交换位置来达到排序的目的。冒泡排序名字的由来就是因为在交换过程中,类似水冒泡,小(大)的元素经过不断的交换由水底慢慢的浮到水的顶端。
冒泡排序的思想就是利用的比较交换,利用循环将第 i 小或者大的元素归位,归位操作利用的是对 n 个元素中相邻的两个进行比较,如果顺序正确就不交换,如果顺序错误就进行位置的交换。通过重复的循环访问数组,直到没有可以交换的元素,那么整个排序就已经完成了。
算法描述
- 比较相邻的元素,如果前一个比后一个大,交换之。
- 第一趟排序第1个和第2个一对,比较与交换,随后第2个和第3个一对比较交换,这样直到倒数第2个和最后1个,将最大的数移动到最后一位。
- 第二趟将第二大的数移动至倒数第二位
算法分析
首先我们定义一个非顺序排列的数组
根据冒泡排序的思想,我们比较相邻的两位数,如果前面的值比后面的大,则交换。
上图所示的过程,我们发现冒泡排序的几个特性
-
由于只有前面的数字比后面的数字大才会进行交换操作,如果一个数字比其后一位的数字小,一次冒泡排序之后,该数字仍然在比它大的数字的前面。
-
第一轮交换之后,并不能保证数组是有序的
-
最大的数字会移动到最后,因为最大的数始终大于其他数字,所以一直会进行交换操作,交换操作是当前位置的数字与后一位进行交换,交换完毕,索引值加一,然后下一次判断时,之前被我们换到后面的数字又要进行判断,这个过程一直重复直至我们的索引值移动到数组的尾部。
我么继续下一轮的冒泡
我们发现是实际上7无需和8进行比较,而且数组中第二大的元素7移动到了倒数第二位,这时候我们可以进行优化,每次冒泡排序的下标所能到达的最大值是上一次的减1,我们开始后面几次的遍历
我们又发现了一个问题:
- 当倒数第二次,数组已经有序了,还是会进行条件判断,因为计算机并不理解什么是有序无序,只是根据我们的if(条件)是否成立去执行程序而已,所以这里我们还需要优化。
至此,我们的冒泡排序的分析思路结束了,接下来我们用代码来实现
代码实现
交换数字
由于第一个和第二个数比,第一项要小,所以结果不变
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
//如果0大于1,交换
if (array[0]>array[1]){
//定义中间变量,用于交换
int temp=array[0];
array[0]=array[1];
array[1]=temp;
}
System.out.println(Arrays.toString(array));
}
[1, 5, 8, 7, 2, 6]
下标移动
我们判断第三和第四个数,对应的下标是 2 和 3
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
//如果2大于3,交换
if (array[2]>array[3]){
//定义中间变量,用于交换
int temp=array[2];
array[2]=array[3];
array[3]=temp;
}
System.out.println(Arrays.toString(array));
}
[1, 5, 7, 8, 2, 6]
下标最大值
按照这个逻辑我们一直将比较的数的下标增加,那么时候结束呢,我们看一下下面的程序
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
//如果0大于1交换
if (array[6]>array[7]){
//定义中间变量,用于交换
int temp=array[6];
array[6]=array[7];
array[7]=temp;
}
System.out.println(Arrays.toString(array));
}
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6
at net.dulao.Demo1.main(Demo1.java:20)
我们的初始下标是0,每次都和后一位进行比较,所以下标达到数组最大长度-1时,是最后一次了。
至此我们的基本逻辑分析完毕,我们走一遍代码。
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
for (int i = 0; i < array.length-1; i++) {
//如果0大于1交换
if (array[i]>array[i+1]){
//定义中间变量,用于交换
int temp=array[i];
array[i]=array[i+1];
array[i+1]=temp;
}
System.out.println(Arrays.toString(array));
}
}
[1, 5, 8, 7, 2, 6]
[1, 5, 8, 7, 2, 6]
[1, 5, 7, 8, 2, 6]
[1, 5, 7, 2, 8, 6]
[1, 5, 7, 2, 6, 8]
每一趟冒泡排序的最大下标
这是第一趟排序,我们看到最大的8像泡泡一样的往上浮动,直至达到数组的尾部,我们开始第二次冒泡排序,这次我们i能到达的最大值是上一次的-1,也就是array.length-2;
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
for (int i = 0; i < array.length-1; i++) {
//如果0大于1交换
if (array[i]>array[i+1]){
//定义中间变量,用于交换
int temp=array[i];
array[i]=array[i+1];
array[i+1]=temp;
}
//System.out.println(Arrays.toString(array));
}
for (int i = 0; i < array.length-2; i++) {
//如果0大于1交换
if (array[i]>array[i+1]){
//定义中间变量,用于交换
int temp=array[i];
array[i]=array[i+1];
array[i+1]=temp;
}
System.out.println(Arrays.toString(array));
}
[1, 5, 7, 2, 6, 8]
[1, 5, 7, 2, 6, 8]
[1, 5, 2, 7, 6, 8]
[1, 5, 2, 6, 7, 8]
我们发现第二次循环的开始值也是0,结束时上一次结束的最大索引-1,于是我们可以把循环嵌套起来
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
for (int i = 0; i < array.length-1; i++) {
for (int j = 0; j < array.length-1-i; j++) {
//如果0大于1交换
if (array[j]>array[j+1]){
//定义中间变量,用于交换
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
//我们的逻辑已经清晰,所以打印语句移动内侧循环结束之后再打印
System.out.println(Arrays.toString(array));
}
}
排序优化
我们外侧循环i可以代表循环的次数,也就是冒泡的次数,一共要冒泡排序array.length-1从,
循环里面记录的是具体每一趟冒泡排序的开始值,结束值,我们发现第一次的i为0,我们可以让循环的终止条件改为array.length-i-1,这样第一次冒泡排序也能让最大下标为array.length-1,第二次array.length-2…
我们的冒泡排序的代码就算完成了,我们跑一遍。
[1, 5, 7, 2, 6, 8]
[1, 5, 2, 6, 7, 8]
[1, 2, 5, 6, 7, 8]
[1, 2, 5, 6, 7, 8]
嗯,完成是完成了,但是我们看最后两次是重复的,我们需要给冒泡排序加一个结束条件:如果一次完整的冒泡排序之后,没有数字进行交换,说明已经排好了,我们跳出循环。
我们立一个flag,如果进入if的语句块中,说明在进行交换,我们立flag失败,置为false。如果没走if的语句那么flag还是true,我们进行一些操作,这是我们的优化思路
boolean flag=true;
if (array[j]>array[j+1]){
//定义中间变量,用于交换
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
flag=false;
}
if(flag){
......
}
我们的flag应该立在两个for之间,也就是说一趟冒泡排序之后,我们的flag都为true,说明没有一次if是成立的,数组已经有序,我们break跳出外圈循环
最终代码
public static void main(String[] args) {
//接收一个数组
int[] array={1,5,8,7,2,6};
for (int i = 0; i < array.length-1; i++) {
boolean flag=true;
for (int j = 0; j < array.length-1-i; j++) {
//如果0大于1交换
if (array[j]>array[j+1]){
//定义中间变量,用于交换
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
flag=false;
}
}
if (flag){
break;
}
System.out.println(Arrays.toString(array));
}
}
[1, 5, 7, 2, 6, 8]
[1, 5, 2, 6, 7, 8]
[1, 2, 5, 6, 7, 8]
这样我们的代码逻辑就算完成了。
总结
总结个锤子,黑科技还没上呢!
冒泡排序源码
public class BubbleSort implements SortAlgorithm {
/**
* 排序
*
* @param array 数组
*/
@Override
public void sort(int[] array) {
for (int i = 0; i < array.length; i++) {
boolean flag=true;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j]>array[j+1]){
//交换
reverse(array,j,j+1);
flag=false;
}
}
//如果一次冒泡排序没发生数据交换,说明一件排序好了;
if(flag){
return ;
}
}
}
}
测试
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
//可行性测试
simpleTest(BubbleSort.class);
//压力测试
timeComplexityTest(BubbleSort.class);
}
===================BubbleSort=====================
原数组[-361, -452, 485, 326, 239, -104, -19, -102, -426, 221]
排序后数组[-452, -426, -361, -104, -102, -19, 221, 239, 326, 485]
排序结果正确!
开始计算....
BubbleSort:11204ms
怎么用,有点意思吧,关于这个测试方法的讲解我放在了这里
知识总结:
-
冒泡排序法:也叫升序排序法,但是相比起二分法查找只能应用于有序数列,二如何将一个无序数列变的有序就可以使用冒泡排序法!!!
-
**空间时间复杂度:**通过分析冒泡排序的实现代码可以得知,该算法的最差时间复杂度为
O(n2)
,最优时间复杂度为O(n)
,平均时间复杂度为O(n2)
。 -
优化:一趟冒泡排序没交换操作则表示排列完成
彩蛋:
//假如我们不加这一行
if(flag){
// return ;
}
原数组[289, 425, 3, 131, -197, 140, 197, -382, -165, 260]
排序后数组[-382, -197, -165, 3, 131, 140, 197, 260, 289, 425]
排序结果正确!
开始计算....
BubbleSort:10634ms
额,这个优化就意思一下,可能快个5%吧。