冒泡排序,冒泡排序的比较次数、冒泡排序的优化

发布时间 2023-06-28 00:31:25作者: 白露~

冒泡排序及其优化

冒泡排序是一种简单而经典的排序算法,它的基本思想是通过两两比较相邻元素的大小,将较大的元素逐步向后移动,从而实现从小到大的排序。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1),是一种稳定的排序算法。

冒泡排序的原理

冒泡排序的原理可以用以下图示来说明:

 

如图所示,假设我们要对数组[3, 2, 7, 4, 1]进行升序排序,我们可以采用以下步骤:

  • 第一趟冒泡:从左到右依次比较相邻的两个元素,如果前一个元素大于后一个元素,则交换它们的位置。这样一趟下来,最大的元素就被移动到了最右边。
  • 第二趟冒泡:由于最右边的元素已经是最大的了,所以不需要再比较它,只需要比较前面四个元素。同样地,从左到右依次比较相邻的两个元素,如果前一个元素大于后一个元素,则交换它们的位置。这样一趟下来,第二大的元素就被移动到了倒数第二个位置。
  • 第三趟冒泡:由于最右边的两个元素已经是最大的了,所以不需要再比较它们,只需要比较前面三个元素。同样地,从左到右依次比较相邻的两个元素,如果前一个元素大于后一个元素,则交换它们的位置。这样一趟下来,第三大的元素就被移动到了倒数第三个位置。
  • 第四趟冒泡:由于最右边的三个元素已经是最大的了,所以不需要再比较它们,只需要比较前面两个元素。同样地,从左到右依次比较相邻的两个元素,如果前一个元素大于后一个元素,则交换它们的位置。这样一趟下来,第四大的元素就被移动到了倒数第四个位置。
  • 第五趟冒泡:由于最右边的四个元素已经是最大的了,所以不需要再比较它们,只需要比较前面一个元素。但是由于前面只有一个元素了,所以不需要进行任何操作。这样一趟下来,第五大的元素就被移动到了倒数第五个位置。

经过五趟冒泡后,数组就被排成了升序。

冒泡排序的Java实现

根据上面的原理,我们可以用Java语言来实现冒泡排序算法。我们需要用两层循环来控制比较和交换的过程:

  • 外层循环控制冒泡的趟数,由于每趟冒泡都能确定一个元素在数组中的最终位置,所以最多只需要n-1趟冒泡,其中n是数组的长度。
  • 内层循环控制每趟冒泡的比较范围,由于每趟冒泡都能将最大的元素移动到最右边,所以每趟冒泡的比较范围都会缩小一位,即从0到n-i-1,其中i是当前的趟数。

以下是冒泡排序的Java代码实现:

// 冒泡排序 (Java)
public static void bubbleSort(int[] arr) {
    int len = arr.length; // 数组长度
    int temp; // 临时变量,用于交换
    for (int i = 0; i < len - 1; i++) { // 外层循环,控制趟数
        for (int j = 0; j < len - 1 - i; j++) { // 内层循环,控制比较和交换
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换位置
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

冒泡排序的优化

虽然冒泡排序的原理很简单,但是它的效率并不高,因为它需要进行大量的比较和交换操作。为了提高冒泡排序的性能,我们可以采用一些优化方法,例如:

  • 设置一个标志位,记录每趟冒泡是否发生了交换,如果没有交换,说明数组已经有序,可以提前结束循环。
  • 记录每趟冒泡最后发生交换的位置,这个位置之后的元素已经有序,下一趟只需要比较到这个位置即可。
  • 在每趟冒泡中同时进行正向和反向的比较和交换,可以减少冒泡的趟数。

优化方法①:设置标志位

如果数组本身就是有序的,或者在某一趟冒泡中没有发生任何交换,那么说明数组已经有序了,没有必要再进行后续的循环。为了检测这种情况,我们可以设置一个布尔型的标志位,在每趟冒泡开始前将其设为false,在每次交换时将其设为true。如果一趟冒泡结束后,标志位仍然为false,那么说明没有发生任何交换,可以直接跳出循环。

以下是使用标志位优化后的Java代码实现:

// 冒泡排序优化方法① (Java)
public static void bubbleSort(int[] arr) {
    int len = arr.length; // 数组长度
    int temp; // 临时变量,用于交换
    boolean exchange; // 标志位,用于记录是否发生交换
    for (int i = 0; i < len - 1; i++) { // 外层循环,控制趟数
        exchange = false; // 每趟开始前将标志位设为false
        for (int j = 0; j < len - 1 - i; j++) { // 内层循环,控制比较和交换
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换位置
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                exchange = true; // 发生了交换,将标志位设为true
            }
        }
        if (!exchange) { // 如果一趟结束后标志位仍为false,说明没有发生任何交换,可以跳出循环
            break;
        }
    }
}

优化方法②:记录最后交换的位置

在每趟冒泡中,最后一次发生交换的位置之后的元素都是有序的,因为它们都没有参与过交换。所以我们可以记录每趟冒泡最后发生交换的位置,作为下一趟冒泡的终点,这样可以减少不必要的比较次数。

以下是使用最后交换位置优化后的Java代码实现:

// 冒泡排序优化方法② (Java)
public static void bubbleSort(int[] arr) {
    int len = arr.length; // 数组长度
    int temp; // 临时变量,用于交换
    int flag = len; // 标志位,用于记录最后交换的位置
    for (int i = 0; i < len - 1; i++) { // 外层循环,控制趟数
        int k = flag; // 记录当前循环的终点
        flag = 0; // 重置标志位
        for (int j = 0; j < k - 1; j++) { // 内层循环,控制比较和交换
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换位置
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = j + 1; // 更新最后交换的位置
            }
        }
        if (flag == 0) { // 如果一趟结束后标志位仍为0,说明没有发生任何交换,可以跳出循环
            break;
        }
    }
}

优化方法③:双向冒泡

在每趟冒泡中,我们不仅可以从左到右比较和交换元素,将最大的元素移动到最右边,还可以从右到左比较和交换元素,将最小的元素移动到最左边。这样可以同时确定两个元素在数组中的最终位置,减少冒泡的趟数。

以下是使用双向冒泡优化后的Java代码实现:

// 冒泡排序优化方法③ (Java)
public static void bubbleSort(int[] arr) {
    int len = arr.length; // 数组长度
    int temp; // 临时变量,用于交换
    int left = 0; // 左边界
    int right = len - 1; // 右边界
    while (left < right) { // 当左右边界相遇时结束循环
        for (int i = left; i < right; i++) { // 正向冒泡,将最大的元素移动到右边界
            if (arr[i] > arr[i + 1]) { // 如果前一个元素大于后一个元素,交换位置
                temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
        right--; // 右边界左移一位
        for (int i = right; i > left; i--) { // 反向冒泡,将最小的元素移动到左边界
            if (arr[i] < arr[i - 1]) { // 如果后一个元素小于前一个元素,交换位置
                temp = arr[i];
                arr[i] = arr[i - 1];
                arr[i - 1] = temp;
            }
        }
        left++; // 左边界右移一位
    }
}

冒泡排序的性能分析

冒泡排序的时间复杂度和空间复杂度如下:

  • 时间复杂度:O(n2),其中n是数组的长度。冒泡排序需要进行n-1趟冒泡,每趟冒泡需要比较n-i次,其中i是当前的趟数。所以总的比较次数为(n-1)+(n-2)+…+1=(n-1)*n/2,即O(n2)。优化方法可以减少比较次数,但是时间复杂度的量级不变。
  • 空间复杂度:O(1),冒泡排序只需要一个临时变量来交换元素,所以空间复杂度为O(1)。

冒泡排序是一种稳定的排序算法,即相等的元素在排序后不会改变它们的相对顺序。

冒泡排序的应用场景

冒泡排序是一种简单而经典的排序算法,它的优点是实现简单,代码易于理解,不需要额外的空间,且稳定。它的缺点是效率低,比较和交换次数多,对于大规模或者近乎有序的数据不适合。因此,冒泡排序适合用于小规模或者基本有序的数据,或者作为教学和学习的入门算法。

总结

本文介绍了冒泡排序的原理、实现、优化和性能分析,以及它的应用场景。冒泡排序是一种基础的排序算法,掌握它有助于理解其他更高效的排序算法。希望本文对你有所帮助,欢迎留言交流。