冒泡排序(Bubble Sort)
基本思想:
依次比较相邻的两个数,将小数放在前面,大数放在后面。即首先比较第一个和第二个数,将小数放前面,大数放后面。然后继续比较第二个数和第三个数,将小数放前面,大数放后面,直到比较完最后面两个数。这样一趟下来就将最大的一个数放在了最末尾。接着进行下一趟排序,将次大的数放在倒数第二个位置。重复这个过程,直到完成最终排序。
算法实现:
/**
* 冒泡排序
* @param arr 待排序数组
*/
public static void bubbleSort(int[] arr) {
int length = arr.length; // 数组长度
int temp = 0; // 用于交换数值的中间变量
for (int i = 0; i < length - 1; i++) { // 外层循环,只需 length-1 次即可
for (int j = 0; j < length - 1 - i; j++) { // 内层循环,次数为length - 1 - i
if (arr[j] > arr[j + 1]) { // 如果前面的数大于后面的数,进行交换,实现升序排列;反之实现降序排列
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
原始算法的不足体现在两个方面:
1、在数组已经有序后,仍然需要进行循环判断。
一个大小为 N 的数组初始有序,按原始算法对它进行排序必须进行 N - 1 趟外层循环以及每一趟中的N - 1、N - 2、N - 3..1次内层循环比较,其实在一趟比较之中只要没有出现前面的数大于后面的数就可以判断数组已经有序了,后面的 98 趟外层循环以及内部的比较是可以省略的。
2、每次循环比较的次数都只是在前一次的基础上少 1。
然而会有这样的情况,一趟比较交换之后,后面很多数都已经有序,就前面少量的数无序,按照原始的冒泡排序算法,必须完成 N - 1 - i (即外层循环中的 i)次的比较,然而实际上只需要比较前面几个数并交换就可以了。比较的次数是可以相对减少的。比如一个数组初始为 5、4、6、7 经过一趟的 3 次比较后变成了 4、5、6、7,下一趟只需进行一次比较就可以了。
理论上可以对其进行的优化:
1、增加标志位,避免不必要的循环比较
2、使用一个变量记录内层循环比较中最后进行交换的下标,并将它作为下趟循环比较的次数
但是,这里的优化主要是针对极端情况进行的,比如数组已经有序,这种情况下效率提升自然是十分明显的。
对含有 100 万个元素的有序数组进行排序:
但是实际情况中,其实效率表现都差不多,反而可能因为多出来的开销导致效率更低。
对含有 100 万个元素的无序数组进行排序:
这种情况下,“优化”过的冒泡排序比原始冒泡排序还慢了差不多 3 分钟。
可见冒泡排序的优化在正常情况下其实并没有起到提升效率的作用。
普通的使用中,还是得看平均效率。就这一点而言,所谓的“优化”并没有起到太大作用。况且,冒泡排序本身的效率就很低下,数据量很大时,再怎么对冒泡排序进行优化也不会说有特别高的效率。
经过“优化”后的冒泡排序算法:
/**
* 冒泡排序
* @param arr 待排序数组
*/
public static void bubbleSort(int[] arr) {
int length = arr.length; // 数组长度
int temp = 0; // 用于交换数值的中间变量
int index = 0; // 用于记录下次循环中需要比较的次数
int mark = length - 1; // 内层循环的次数,初始为 length - 1
boolean isOrdered = false; // 标志位,表示数组是否已经有序,有序则停止排序
for (int i = 0; i < length - 1; i++) { // 外层循环,只需循环 length-1 次即可
isOrdered = true;
for (int j = 0; j < mark; j++) { // 内层循环。原始为length - 1 - i
if (arr[j] > arr[j + 1]) { // 如果前面的数大于后面的数,进行交换,实现升序排列;反之实现降序排列
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isOrdered = false;
index = j; // 代表下次内层循环中需要比较的次数,当 arr[j] 不再大于 arr[j+1] 时,说明 arr[j] 以及之后是有序的,不需要再次比较
}
}
mark = index; // 下次内层循环的次数
if (isOrdered) // 若已经有序,跳出外层循环,排序结束
break;
}
}
算法测试:
package algorithmAndDesignPatterns;
import java.util.Random;
/**
* 优化后的冒泡排序
* @author Wll
*
*/
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = getRandomArray(6, 20);
// int[] arr = {1, 2, 3, 4, 5, 6}; // 测试数组
System.out.print("原始数组: ");
PrintArray(arr);
System.out.println("====开始冒泡排序====\n");
BubbleSort(arr); // 冒泡排序
System.out.print("排序结果:");
PrintArray(arr);
}
/**
* 冒泡排序,并打印每一趟排序后的情况
* @param arr 待排序数组
*/
private static void BubbleSort(int[] arr) {
int length = arr.length; // 数组长度
int temp = 0; // 用于交换数值的中间变量
int index = 0; // 用于记录下次循环中需要比较的次数
int mark = length - 1; // 初始内层循环的次数
boolean isOrdered = false; // 标志位,表示数组是否已经有序,有序则停止排序
int count;
for (int i = 0; i < length - 1; i++) { // 外层循环,只需循环 length-1 次即可
if (!isOrdered)
System.out.println("第 " + (i + 1) + " 趟排序:");
isOrdered = true;
count = 0;
for (int j = 0; j < mark; j++) { // 内层循环。原始为length - 1 - i
if (arr[j] > arr[j + 1]) { // 如果前面的数大于后面的数,进行交换,实现升序排列;反之实现降序排列
System.out.println(arr[j] + "<-->" + arr[j + 1]); // 打印交换过程
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isOrdered = false;
index = j; // 代表下次内层循环中需要比较的次数,当 arr[j] 不再大于 arr[j+1] 时,说明 arr[j] 以及之后是有序的,不需要再次比较
}
}
mark = index; // 下次内层循环的次数
// 测试经过交换后是否已经有序,count最终为0表示已经有序
for (int j = 0; j < mark; j++) {
if (arr[j] > arr[j + 1])
count++;
}
System.out.print("结果: ");
PrintArray(arr);
if (count == 0) // 已经有序
isOrdered = true;
if (isOrdered) { // 为了满足自己想要的打印效果,多使用了一个 int 变量 count。 强迫症怎么得了
System.out.println("===已经有序,排序结束===");
System.out.println();
break;
}
}
}
/**
* 打印数组
* @param arr 待打印数组
*/
private static void PrintArray(int[] arr) {
int length = arr.length;
for (int i = 0; i < length; i++)
System.out.print(arr[i] + " ");
System.out.println("\n");
}
/**
* 获得一个随机整型数组
* @param size 数组大小
* @param limit 数组元素大小的上限
* @return 一个随机数组
*/
private static int[] getRandomArray(int size, int limit) {
int[] arr = new int[size];
Random random = new Random();
for (int i = 0; i < size; i++)
arr[i] = random.nextInt(limit);
return arr;
}
}
运行情况: