大O - 冒泡排序

时间:2017-12-24 20:59:33

标签: java algorithm sorting big-o

我写了两个不同版本的冒号排序算法 - bubbleSort,你在教科书中看到的算法的传统版本,以及sortIntArray,它与{{1}非常相似但是是递归的。

也许这是我的误解,但后一种算法调用本身让我觉得算法的效率不同于冒泡排序。有人可以向我解释这两者之间的区别吗?

bubbleSort
private static int[] sortIntArray(int[] arr) {

    for(int i=0; i<arr.length-1; i++) {
        if(arr[i+1]<arr[i]) { // [i-2], [i-1], [i], [i+1], [i+2]
            printArr(arr);
            int temp = arr[i];
            arr[i] = arr[i+1];
            arr[i+1] = temp;
            sortIntArray(arr);
        }
    }
    return arr;
}
private static int[] bubbleSort(int[] arr) {

    boolean swap = false;
    while(!swap) {
        swap = true;
        for(int i = 0; i<arr.length-1; i++) {
            if(arr[i+1]< arr[i]) {
                printArr(arr);
                swap = false;
                int temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
            }
        }
    }
    return arr;
}

2 个答案:

答案 0 :(得分:4)

实际上,新实施似乎效率较低。传统的冒泡排序比较每次传递中的每对相邻元素,并且基本上将每个传递中最大的未排序元素过滤到数组中未排序部分的末尾。通道包括外环。

您的函数模拟以下迭代版本(实际上是插入排序):

for (int i = 1; i < a.size(); ++i) {
    for (int j = i; j >= 1; --j) {
        if (a[j] < a[j - 1]) {
            int tmp = a[j];
            a[j] = a[j - 1];
            a[j - 1] = tmp;
        }
        else {
            break;
        }
    }
}

由于您在每次交换时调用递归函数,使用相同的数组并且递归调用在同一索引处开始处理,因此您基本上模拟了一个额外的循环(可以{{3}使用递归调用将所遇到的反转从开头排序到该点。

可以这样想:对于您遇到的每次反转,您都会有一个递归调用。如果在循环中的任何点期间,您有一个数组的初始排序部分,例如,让此函数调用为f_1
2 3 7. 1 4 5 6

.表示数组已排序到该点。现在请注意,当循环达到7和1时,它会交换并递归调用函数,然后重复,这样1最终会在前面结束(让我们说这发生的调用是{{1} }),数组变为:

f_2

请注意1 2 3 7. 4 5 6的深度超过f_2。此时,f_1不会返回,但是循环从1继续前进,这有效地模拟了f_2,但更大的部分排序了,这意味着当调用f_1时返回,整个数组将被排序。因此,f_2之前的额外递归调用不会做任何事情,只会占用堆栈空间。

算法的空间复杂度最差情况f_2,时间复杂度为O(n ^ 3),效率低于原始值(对于时间复杂度,请考虑反向排序的数组大小O(n^2)具有n个反转,因此n^2递归调用,每个调用遍历整个数组,因此n^2)。

答案 1 :(得分:3)

我添加了一个静态计数器变量,我在每个方法中每次传递最内层循环时都会增加。因此对于sortIntArray,计数器的摘录将是:

for(int i=0; i<arr.length-1; i++) {
        o++;
        if(arr[i+1]<arr[i]) { // [i-2], [i-1], [i], [i+1], [i+2]
            ...

我还实现了一个不同的方法,基本上是维基百科中提供的冒泡排序,我改变了你的随机数组生成器来生成不同大小的数组:

private static int[] getIntArray() {
    int[] rand;
    if (MAX_SIZE - MIN_SIZE > 0) {
        rand = new int[new Random().nextInt(MAX_SIZE - MIN_SIZE) + MIN_SIZE + 1];
    } else {
        rand = new int[MIN_SIZE];
    }
    for (int i = 0; i < rand.length; i++) {
        rand[i] = new Random().nextInt(rand.length * 2);
    }
    return rand;
}

当列表大小为50时,这是一个示例结果。如果您自己构建测试代码,您将看到维基百科的实现不仅可以保证排序,而且还提供了一个很好的基准:

Unsorted: [52, 48, 62, 47, 42, ...]
n: 50
Bubble sort: [3, 4, 6, 6, 11, ...]
O(n): 1960
Wikipedia sort: [3, 4, 6, 6, 11, ...]
O(n): 2450
Recursive sort: [3, 4, 6, 6, 11, ...]
O(n): 27391

有趣的是,可以清楚地看到递归排序执行的是其他排序算法的更多迭代。基于10,000种类型的数据,我将其转储到CSV,看起来最大尺寸为30,n ^ 2项的系数是apx。 11.3对于严格的2阶多项式拟合,而对于最大值50,它增加到大约19,这表明没有这样的m使得m * n ^ 2是算法的运行时复杂性的上限。 / p>

tl; dr:根据经验,递归算法的时间比O(n ^ 2)差。