冒泡排序
排序步骤(有n个数需要从小到大排序)
1、相邻的元素之间相互比较,较小数前移,较大数后移,相等则不变
从前往后进行第一遍排序不会有序,但是最后一位的数字一定会是这组数中最大的
那么就可以将排序的范围缩小一位,范围缩小到1~n-1
2、对于这一范围的数,从前往后再次进行相邻两个数的比较
此时n-1位置上一定是1~n-1范围上最大的数
再次将排序的范围缩小一位,范围缩小到1~n-2
3、......
重复上述步骤直到最后一对数字(也就是第一个和第二个数字)比较完成
对于从大到小排序的冒泡排序来说相反,是将最小的一个数向前移动到最前面,缩小范围时剔除最前面的一个数,重复步骤直到第n-1个数字和第n个数字比较完成
解析说明
冒泡排序需要嵌套两层for循环来控制:外层循环控制已经排好序的数字的个数,内层循环控制剩余所需要排序的范围;
外层循环 n 次,内层最多时循环 n – 1次、最少循环 0 次,平均循环(n-1)/2;
循环体内总的比较交换次数为:n*(n-1) / 2 = (n^2-n)/2 ;
时间复杂度为O(N^2) ;
最优的空间复杂度为开始元素已排序,则空间复杂度为 0;
最差的空间复杂度为开始元素为逆排序,则空间复杂度为 O(N);
平均的空间复杂度为O(1) .
public static int[] bubblingSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {//外层排序:确定已经排好序的数字的个数
for (int j = 0; j < arr.length - i; j++) {//内层排序:确定当次排序的范围
if (arr[j] < arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
return arr;
}
优化
场景一:在某次遍历中如果没有数据交换,说明整个数组已经有序。若初始序列就是排序好的,如果用基础的冒泡排序方法,仍然还要比较O(N^2)次,但无交换次数。
改进思路:通过设置标志位来记录此次遍历有无数据交换,进而可以判断是否要继续循环,设置一个flag标记,当在一趟序列中没有发生交换,则该序列已排序好,但优化后排序的时间复杂度没有发生量级的改变。
//优化1:在排序的过程中判断是否已经排好序,若排好序,则退出
public static int[] bubblingSort1(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean flag = true;//默认当前已经排好序
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] < arr[j + 1]) {
swap(arr, j, j + 1);
flag = false;//发生交换,说明并未排好序
}
}
if (flag) {//已经排好序,退出排序
break;
}
}
return arr;
}
场景二:如果有100个数的数组,仅前面10个无序,后面90个都已排好序且都大于前面10个数字,那么在第一趟遍历后,最后发生交换的位置必定小于10,且这个位置之后的数据必定已经有序了。
改进思路:记录某次遍历时最后发生数据交换的位置pos,这个位置之后的数据显然已经有序了。因此通过记录最后发生数据交换的位置就可以确定下次循环的范围了。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可
//优化2:将已经排好序的部分剔除,仅考虑未排好序的部分,实现优化
public static int[] bubblingSort2(int[] arr) {
int pos = arr.length;//记录最后交换的数的位置
int k = pos;
while (pos > 0) {
k = pos;//通过k来记录最后排序的数字的位置
pos = 0;//为了避免死循环
for (int i = 1; i < k; i++) {//通过记录最后发生数据交换的位置就可以确定下次循环的范围了
if (arr[i - 1] > arr[i]) {
swap(arr, i - 1, i);
pos = i;
}
}
}
return arr;
}
冒泡排序代码
import org.junit.Test;
public class BubblingSort {
@Test
public void test1() {
int[] arr = new int[]{12, 15, 4, 5, 8, 35};
bubblingSort2(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
//优化1:在排序的过程中判断是否已经排好序,若排好序,则退出
public static int[] bubblingSort1(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean flag = true;//默认当前已经排好序
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] < arr[j + 1]) {
swap(arr, j, j + 1);
flag = false;//发生交换,说明并未排好序
}
}
if (flag) {//已经排好序,退出排序
break;
}
}
return arr;
}
//优化2:将已经排好序的部分剔除,仅考虑未排好序的部分,实现优化
public static int[] bubblingSort2(int[] arr) {
int pos = arr.length;//记录最后交换的数的位置
int k = pos;
while (pos > 0) {
k = pos;//通过k来记录最后排序的数字的位置
pos = 0;//为了避免死循环
for (int i = 1; i < k; i++) {//通过记录最后发生数据交换的位置就可以确定下次循环的范围了
if (arr[i - 1] > arr[i]) {
swap(arr, i - 1, i);
pos = i;
}
}
}
return arr;
}
public static void swap(int[] data, int a, int b) {
int temp = data[a];
data[a] = data[b];
data[b] = temp;
}
}