深入冒泡排序:揭开时间复杂度的神秘面纱!

  • 冒泡排序专业解释

冒泡排序是一种简单的排序算法,它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

  •  通俗易懂的解释

想象一个水池,里面有许多大小不一的泡泡。每一次我们都从水底开始,比较两个相邻的泡泡的大小。较大的泡泡会上浮,这样每一轮比较后,最大的泡泡都会被推到最上面。重复这个过程,直到所有的泡泡都按照大小有序排列。

  • 时间复杂度分析

比较次数:对于一个长度为n的列表,最多需要n-1次外部遍历。

交换次数:在最坏的情况下,即列表完全逆序,需要n(n-1)/2次交换。

时间复杂度:最好情况(已经排序)为O(n),最坏情况(完全逆序)为O(n^2),平均情况也是O(n^2)。

  • 例子

考虑一个简单的整数列表:[5, 4, 3, 2, 1],我们进行冒泡排序,步骤如下:

第1轮:

比较5和4,交换他们。得到:[4, 5, 3, 2, 1]

比较5和3,交换他们。得到:[4, 3, 5, 2, 1]

比较5和2,交换他们。得到:[4, 3, 2, 5, 1]

比较5和1,交换他们。得到:[4, 3, 2, 1, 5]

在第1轮结束后,最大的数字5已经在正确的位置上了。

第2轮:

比较4和3,交换他们。得到:[3, 4, 2, 1, 5]

比较4和2,交换他们。得到:[3, 2, 4, 1, 5]

比较4和1,交换他们。得到:[3, 2, 1, 4, 5]

在第2轮结束后,次大的数字4已经在正确的位置上了。

第3轮:

比较3和2,交换他们。得到:[2, 3, 1, 4, 5]

比较3和1,交换他们。得到:[2, 1, 3, 4, 5]

在第3轮结束后,数字3已经在正确的位置上了。

第4轮:

比较2和1,交换他们。得到:[1, 2, 3, 4, 5]

在第4轮结束后,数字2已经在正确的位置上了。

在这四轮之后,数组[5, 4, 3, 2, 1]已经被成功地冒泡排序为[1, 2, 3, 4, 5]。我们发现,要完成对5个数据的排序,需要4轮。那么要完成对n个数据的排序,最多需要(n-1)轮

下面是java代码的实现方式:

public void bubbleSort(int[] arr) {
    int n = arr.length; // 数组的长度
    for (int i = 0; i < n - 1; i++) { 
        for (int j = 0; j < n - i - 1; j++) { 
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

以上for循环的时间复杂度分析,和之前讲的有些区别。我们之前讲的双for循环:外层循环每执行一次,内层会执行n次。

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
          …
        }
}

但是在我们上面实现的冒泡排序代码中,可以发现,内层循环运行的次数会随着外层循环的每次迭代而减少。例如,当i=0时,内循环运行了n-1次;当i=1时,内循环运行了n-2次,依此类推,内层循环并不是固定的n次

那么该如何计算呢?代码运行了多少次呢?

我们可以把所有的情况都列出来,然后加在一起,就是代码的运行总次数。当i=0时,内循环运行了n-1次;当i=1时,内循环运行了n-2次……

我们把所有的次数加起来:(n-1) + (n-2) + ... + 1 = n(n-1)/2,这实际上是一个算数级数,它的求和公式是n(n-1)/2,所以,最后我们可以得到,这段代码的时间复杂度是O(n^{2})。当然,这是最坏的情况,进行了n-1次冒泡排序;最好的情况我们只需要进行1次冒泡排序就可以了。

刚刚讲的代码其实还可以优化,当某次冒泡操作已经没有数据交换时,说明已经达到有序,这里有另一个例子,这里面给6个元素排序,只需要三次冒泡操作就可以了。

为了避免没有交换,我们还进行一次排序的操作,那么我们就把代码优化如下:

public void bubbleSort(int[] arr) {
    int n = arr.length;
    boolean swapped;
    for (int i = 0; i < n - 1; i++) {
        swapped = false;
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;
            }
        }
        // 如果在这一轮中没有发生任何交换,说明数组已经排序完毕
        if (!swapped) break;
    }
}

其中,swapped 变量起到了一个非常重要的作用。

第一:跟踪每一轮是否发生交换:每次元素需要交换时,swapped 被设置为 true。

第二:检查数组是否已经排序完毕:在内循环结束后,如果 swapped 仍然是 false,这意味着在这一轮循环中没有发生任何元素交换。因此,我们可以利用这一特性提前结束排序,从而提高效率。

感谢大家的阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值