【问题标题】:Big O - Bubble sort大 O - 冒泡排序
【发布时间】:2017-12-24 20:59:32
【问题描述】:

我编写了两个不同版本的冒泡排序算法 - bubbleSort,这是您在教科书中看到的算法的传统版本,以及 sortIntArray,它与 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;
}

/**
 * Returns an unordered integer array.
 * @return
 */
private static int[] getIntArray() {
    return new int[]{54, 899, 213, 2, 43, 8, 12, 11, 111, 43, 6, 44, 83, 3458};
}

【问题讨论】:

  • 考虑到boolean swap没有在递归版本中用于结束进程,它可能有不同的运行时间。但不是因为它本身是递归的
  • 他们是一样的!
  • 有一个证据表明您可以将每个递归论坛化为迭代,反之亦然。这不会改变算法的(理论)时间复杂度。
  • @Turing85 可以
  • 递归版本为 O(n^3)。在标准冒泡排序中,可能存在 O(n^2) 交换。对于您找到的每个交换,在您的版本中,您从数组开始重新开始,添加 O(n) 次迭代。

标签: java algorithm sorting big-o


【解决方案1】:

实际上,新的实现似乎效率较低。传统的冒泡排序在每次遍历中比较每对相邻元素,并且本质上在每次遍历中过滤掉最大的未排序元素到数组中未排序部分的末尾。通行证构成外循环。

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

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;
        }
    }
}

由于您在每次交换时调用递归函数,使用相同的数组并且递归调用从相同的索引开始处理,因此您实际上是在模拟一个额外的循环,(可以charged 对遇到的反转进行排序从开始到那一点)使用递归调用。

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

. 表示数组已排序到该点。现在请注意,当循环到达 7 和 1 时,它交换并递归调用该函数,并重复此过程,因此 1 最终在前面结束(假设发生这种情况的调用是 f_2),并且数组变成:

1 2 3 7. 4 5 6

请注意,f_2 的深度比 f_1 更深。此时,f_2 并没有返回,而是从 1 开始循环,这有效地模拟了 f_1,但其中更大的部分是排序的,这意味着当对 f_2 的调用返回时,整个数组将被排序。因此,f_2 之前的额外递归调用只会占用堆栈空间。

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

【讨论】:

  • 它不会模拟您提供的迭代版本。特别是i &lt; arr.size() 是错误的,数组可能会像这样未排序!它需要像i &lt; arr.size() * arr.size()
  • O(n^2) 的效率如何低于标准的bubblesort = O(n^2)?
  • @yassin 至于效率,我指的是他使用的所有额外堆栈空间。上限时间复杂度是相同的(但是如果您尝试理解逻辑,将会有很多额外的迭代;请参阅 Joshua 的回答)。关于实施,我已经更正了;它是 --j 而不是 ++j。该版本实际上是插入排序,而不是冒泡排序。
  • @yassin 更正,时间复杂度为O(n^3),见更新的最后一段。
【解决方案2】:

我添加了一个静态计数器变量,在您的每个方法中,每次通过最内层循环时都会递增该变量。所以对于 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 时的示例结果。如果您自己构建测试代码,您会看到不仅 Wikipedia 实现可以保证排序,而且它还提供了一个很好的基准:

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

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

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

【讨论】:

  • 多么棒的答案。谢谢。我正在尝试尽可能多地了解 Big O。
猜你喜欢
  • 2022-01-11
  • 2021-12-28
  • 2012-07-13
  • 2019-01-13
  • 2016-02-27
  • 2013-10-09
  • 2015-09-12
  • 1970-01-01
  • 2014-03-26
相关资源
最近更新 更多