本文内容和代码均来自于《漫画算法》,小灰和大黄的对话,非常有趣味的一本书。现理论结合实践,做一下测试。
private static final int LEN = 20000;
// 第一版
void bubbleV1(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
int temp = 0;
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
log("第" + (i + 1) + "轮");
log(Arrays.toString(arr));
}
}
/**
* 第一版如果元素经过小于arr.length-1轮比较已经变的有序,依然会进行剩下的轮数比较,
* 为了克服这个缺点,增加一个判断数组是否已经有序的boolean变量。 第二版
*
* @param arr
*/
void bubbleV2(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean isSorted = true;
for (int j = 0; j < arr.length - 1 - i; j++) {
int temp = 0;
log("compare " + arr[j] + " with " + arr[j + 1]);
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isSorted = false;
}
}
if (isSorted) {
break;
}
log("第" + (i + 1) + "轮");
log(Arrays.toString(arr));
}
}
/**
* 第三版 [3,2,1,4,5,6,7,8] [2,1,3,4,5,6,7,8] [1,2,3,4,5,6,7,8]
* 假如这样的初始数组,从4开始的元素已经是有序的了,但是还是白白的比较了许多次。 是一个需要优化的点。 这个问题的关键点在于对数组中有序区的界定。
* 按照现有逻辑,有序区的长度和,排序的轮数是相等的。如第一轮排序过后的有序区长度是1, 第二轮排序过后的有序区长度是2......
* 实际上有序区的长度可能大于这个长度,上述例子中,在第二轮排序时,后面的5个元素已经属于有序区了。 因此后面的多次元素比较是没有意义的。
* 避免方法:在每一轮排序后,记录下最后一次元素交换的位置,该位置即为无需数列的边界,再往后就是有序区了。
*
* @param arr
*/
void bubbleV3(int[] arr) {
int lastExchangedIndex = 0;
int sortBorder = arr.length - 1;
for (int i = 0; i < arr.length - 1; i++) {
boolean isSorted = true;
for (int j = 0; j < sortBorder; j++) {
int temp = 0;
log("compare " + arr[j] + " with " + arr[j + 1]);
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isSorted = false;
lastExchangedIndex = j;
}
}
sortBorder = lastExchangedIndex;
log("border:" + sortBorder);
if (isSorted) {
break;
}
log("第" + (i + 1) + "轮");
log(Arrays.toString(arr));
}
}
/**
* 第四版(鸡尾酒排序) [2,3,4,5,6,7,8,1] 如上例子,前面三种版本排序方法需要排7轮,然而大部分的数据都是有序的。
* 为了克服这个缺点。
*
* @param arr
*/
void bubbleV4(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length / 2; i++) {
// 有序标记,每一轮初始值都是true
boolean isSorted = true;
// 奇数轮,从左往右比较和交换
for (int j = i; j < arr.length - 1 - i; j++) {
log("compare " + arr[j] + " with " + arr[j + 1]);
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isSorted = false;
}
}
if (isSorted) {
break;
}
log("第" + (i + 1) + "轮");
log(Arrays.toString(arr));
// 偶数轮,从右往左比较和交换
isSorted = true;
for (int k = arr.length - 1 - i; k > i; k--) {
if (arr[k] < arr[k - 1]) {
temp = arr[k];
arr[k] = arr[k - 1];
arr[k - 1] = temp;
isSorted = false;
}
}
if (isSorted) {
break;
}
log("第" + (i + 2) + "轮");
log(Arrays.toString(arr));
}
}
@Test
public void test() {
// int[] arr={3,2,1,4,5,6,7,8};
int[] arr = { 2, 3, 4, 5, 6, 7, 8, 1 };
// int[] arr={5,8,6,3,9,2,1,7};
// int[] arr={25, 58, 29, 3, 21, 3, 60, 46, 79, 15};
// bubbleV1(arr);
// bubbleV2(arr);
// bubbleV3(arr);
bubbleV4(arr);
log(Arrays.toString(arr));
}
@Test
public void test2() {
int[] arr2 = new int[LEN];
Random random = new Random();
for (int i = 0; i < LEN; i++) {
arr2[i] = random.nextInt(100);
}
// log(Arrays.toString(arr2));
long begin, end;
begin = System.currentTimeMillis();
bubbleV1(arr2);
end = System.currentTimeMillis();
System.out.println(end - begin);
begin = System.currentTimeMillis();
bubbleV2(arr2);
end = System.currentTimeMillis();
System.out.println(end - begin);
begin = System.currentTimeMillis();
bubbleV3(arr2);
end = System.currentTimeMillis();
System.out.println(end - begin);
begin = System.currentTimeMillis();
bubbleV4(arr2);
end = System.currentTimeMillis();
System.out.println(end - begin);
}
void log(String x) {
if (false) {
System.out.println(x);
}
}
分别用1万个元素和2万个整型数据做测试,结果如下
方法版本 | 数组长度(万) | 耗时(ms) |
---|---|---|
1 | 1 | 2833 |
2 | 1 | 3 |
3 | 1 | 2 |
4 | 1 | 2 |
1 | 2 | 10475 |
2 | 2 | 4 |
3 | 2 | 3 |
4 | 2 | 3 |