冒泡排序算法,基本是我们遇到的第一个排序算法,虽然效率并不高,但毕竟是引入门的算法,还是必须要了解的。
基本思想
在待排序的一组数据中,对还未进行排序的数据,按照从下到上的顺序遍历,依次比较相邻两个数据的大小,将较大(小)值向上冒泡,即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换;循环直到数组中的最后一组数据的比较。此时极大(小)值就出现在了最上边。
基本步骤:
1、外循环是遍历每个元素,每次都放置好一个元素;
2、内循环是比较相邻的两个元素,把大的元素交换到后面;
3、等到第一步中循环好了以后也就说明全部元素排序好了;
算法实现:
/**
* 冒泡排序
* 小->大
*
* @param data 待排序的数组
*/
public static void bubbleSort(int[] data) {
int len = data.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (data[j] > data[j + 1]) {
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
上面也说了,冒泡排序的效率并不高,它的时间复杂度达到了,所以接下来,对这个冒泡排序算法进行一些改进。
改进一:增加位置标识
从冒泡排序的基本实现中可以看到,只有当两个数据的排序不符合我们的排序要求时,才执行交换;那么如果在某一个点位置开始之后的数据没有出现过交换,那么就说明这之后的数据已经符合我们的排序要求,不需要再执行冒泡的比较过程了。
基于这个思想,我们在冒泡算法基础上增加一个位置标识,记录最后一次执行交换的位置,下一次循环只执行到上一次的最后交换位置即可。
改进一实现
/**
* 冒泡排序改进
* 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。
* 由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
*
* @param data 待排序的数组
*/
public static void bubbleSortPos(int[] data) {
int len = data.length;
int i = len - 1; // 初始化最后一个遍历的位置
while (i > 0) {
int pos = 0; // 用于记录最后一次交换的位置
for (int j = 0; j < i; j++) { // 循环遍历只执行到上一次最后交换的位置
if (data[j] > data[j + 1]) {
pos = j;
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
i = pos;
}
}
改进二:增加是否交换过标识
从改进一可以看到,如果有一趟排序循环中,没有出现过交换,那么i=pos=0,直接就退出了while循环,排序结束。由此可以得到,也可以设置一个boolean类型的标识位,如果某一次遍历排序没有出现过交换,证明数组已经符合我们的排序要求了,那么可以结束排序。
改进二实现
/**
* 冒泡排序改进
* 设置一个标识位,如果这一趟冒泡过程中,没有出现过交换,这说明数组中的数据已经是有序的了,可以直接退出排序过程
*
* @param data 待排序的数组
*/
public static void bubbleSortFlag(int[] data) {
int len = data.length;
for (int i = 0; i < len - 1; i++) {
boolean flag = false;
for (int j = 0; j < len - i - 1; j++) {
if (data[j] > data[j + 1]) {
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
flag = true;
}
}
if (!flag) {
break;
}
}
}
改进三:双向冒泡
冒泡排序时,每轮次循环都找到了此次未排序序列中的一个极值,既然一个极值可以向上冒,那么它相对的极值应该可以向下沉才对。带着这个思想,我们对算法进行这样的改进:“一个来回”的遍历排序,找到两个极值,一个极大值,一个极小值,然后将遍历的范围一次缩减两个单位大小,这样就实现了双向的“冒泡”算法。
改进三实现
/**
* 冒泡排序改进
* 双向冒泡排序:正向遍历一遍,冒泡出一个最大值,然后再反向遍历, 冒泡出一个最小值,循环操作,直到最小值位置=最大值位置
*
* @param data 待排序的数组
*/
public static void bubbleSortDouble(int[] data) {
int high = data.length - 1; // 记录上一次的极大值位置
int low = 0; // 记录上一次的极小值位置
int i;
int temp;
while (low < high) {
for (i = low; i < high; i++) { // 正向遍历,找出极大值
if (data[i] > data[i + 1]) {
temp = data[i];
data[i] = data[i + 1];
data[i + 1] = temp;
}
}
high--; // 最高位置下移一位
for (i = high; i > low; i--) { // 方向遍历,找出极小值
if (data[i] < data[i - 1]) {
temp = data[i];
data[i] = data[i - 1];
data[i - 1] = temp;
}
}
low++; // 最低位置上移一位
}
}
最后看下各冒泡排序的结果:
数组大小:100
测试代码:
public void sort() {
int[] data = new int[100];
Random rand = new Random();
for (int i = 0; i < data.length; i++) {
data[i] = rand.nextInt(100);
}
System.out.println("排序之前:" + Arrays.toString(data));
int[] temp1 = data;
Sort.bubbleSort(temp1);
System.out.println("bubbleSort冒泡:" + Arrays.toString(temp1));
temp1 = data;
Sort.bubbleSortPos(temp1);
System.out.println("bubbleSortPos冒泡:" + Arrays.toString(temp1));
temp1 = data;
Sort.bubbleSortFlag(temp1);
System.out.println("bubbleSortFlag冒泡:" + Arrays.toString(temp1));
Sort.bubbleSortDouble(data);
System.out.println("bubbleSortDouble冒泡:" + Arrays.toString(data));
);
}
冒泡排序结果:
冒泡排序时间:
数组大小:10W
测试代码:
public void sort() {
int[] data = new int[100000];
Random rand = new Random();
for (int i = 0; i < data.length; i++) {
data[i] = rand.nextInt(100000);
}
// System.out.println("排序之前:" + Arrays.toString(data));
int[] temp1 = data;
long time0 = System.currentTimeMillis();
Sort.bubbleSort(temp1);
long time1 = System.currentTimeMillis();
long bubbletime = time1 - time0;
// System.out.println("bubbleSort冒泡:" + Arrays.toString(temp1));
temp1 = data;
time0 = System.currentTimeMillis();
Sort.bubbleSortPos(temp1);
time1 = System.currentTimeMillis();
long bubblePostime = time1 - time0;
// System.out.println("bubbleSortPos冒泡:" + Arrays.toString(temp1));
temp1 = data;
time0 = System.currentTimeMillis();
Sort.bubbleSortFlag(temp1);
time1 = System.currentTimeMillis();
long bubbleFlagtime = time1 - time0;
// System.out.println("bubbleSortFlag冒泡:" + Arrays.toString(temp1));
time0 = System.currentTimeMillis();
Sort.bubbleSortDouble(data);
time1 = System.currentTimeMillis();
long bubbleDoubletime = time1 - time0;
// System.out.println("bubbleSortDouble冒泡:" + Arrays.toString(data));
System.out.println("冒泡时间:bubble:" + bubbletime + ",bubblePos:" + bubblePostime + ", bubbleFlag:" + bubbleFlagtime + ",bubbleDouble:" + bubbleDoubletime + ",");
}
结果1:
结果2:
将Double版本的bubble移到前面执行,减少因java内存问题而导致的时间差:
结果3:
结果4:
从上面的执行时间对比结果中可以看到,效率最高的是增加标识Flag以及记录交换位置Pos的两个改进算法,Double双向版本的次之,但也比原始的冒泡排序提高了近10倍。