冒泡排序

159 阅读4分钟

对于排序算法的初学者来说,​冒泡排序因其算法思想非常直观,被认为是八大排序算法中最简单、最易于理解的一个。

🔍 算法思想与步骤

冒泡排序的核心思想是重复地遍历待排序的列表,一次比较两个相邻的元素,如果它们的顺序错误(例如,在升序排序中前一个大于后一个),就将它们交换过来。通过每一轮的遍历,最大(或最小)的元素会像气泡一样逐渐“浮”到数列的顶端,因此得名“冒泡排序”。

其具体步骤如下:

  1. 比较相邻元素​:从列表的第一个元素开始,比较相邻的两个元素。
  2. 交换错误顺序​:如果第一个元素比第二个元素大(对于升序排序),就交换它们的位置。
  3. 移动至下一对​:接着比较下一对相邻元素,重复步骤2,一直进行到列表的末尾。经过这一轮,最大的元素就已经被“冒泡”到了列表的最后一位。
  4. 重复过程,缩小范围​:针对除最后一个(已排好的)元素之外的所有元素,重复上述步骤。每完成一轮,需要比较的元素就减少一个。
  5. 排序完成​:持续进行上述过程,直到没有任何一对数字需要比较,此时列表排序完成。

640.gif

💻 算法实现

下面是冒泡排序的一个基础实现示例(使用类似C语言的语法),它清晰地展示了算法的双循环结构:

void bubbleSort(int arr[], int n) {
    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;
            }
        }
    }
}
  • 外层循环 (i)​​:负责控制排序总共需要进行多少轮。对于一个有 n个元素的列表,最多需要 n-1轮。
  • 内层循环 (j)​​:负责在每一轮中进行实际的“比较和交换”工作。注意 j < n-i-1,这是因为每经过一轮,列表末尾的 i个元素已经是排好序的,无需再比较。

⏱️ 算法特性

  • 时间复杂度​:冒泡排序的平均时间复杂度和最坏时间复杂度都是 ​O(n²)​。这意味着当数据量增大时,排序所需时间会成平方级增加,因此它只适用于小规模数据的排序。
  • 空间复杂度​:为 ​O(1)​。因为它只需要一个临时变量用于交换,属于原地排序,不占用额外的内存空间。
  • 稳定性​:冒泡排序是一种稳定的排序算法。也就是说,如果存在两个相等的元素,排序后它们的相对顺序不会改变。这是因为只有在相邻元素顺序错误时才交换,相等的元素不会相互交换。

🧩 示例演示

以数组 [5, 3, 8, 6, 2]的升序排序为例,过程如下:

  • 第一轮​:

    • 比较 5和3 → 交换 → [3, 5, 8, 6, 2]
    • 比较 5和8 → 不交换 → [3, 5, 8, 6, 2]
    • 比较 8和6 → 交换 → [3, 5, 6, 8, 2]
    • 比较 8和2 → 交换 → [3, 5, 6, 2, 8] (最大数8就位)
  • 第二轮​:在未排序部分 [3, 5, 6, 2]中重复过程,将 6 交换到正确位置 → [3, 5, 2, 6, 8]

  • 第三轮​:在 [3, 5, 2]中,将 5 交换到正确位置 → [3, 2, 5, 6, 8]

  • 第四轮​:在 [3, 2]中,交换 → [2, 3, 5, 6, 8]

  • 排序完成​:[2, 3, 5, 6, 8]

💎 简单对比与其他简单排序

除了冒泡排序,​选择排序插入排序也常被归为简单排序算法,时间复杂度也都是O(n²)。它们的主要区别在于:

  • 选择排序​:思路是每次从未排序部分中找到最小(或最大)元素,放到已排序序列的末尾。它的交换次数比冒泡排序少。
  • 插入排序​:思路类似于整理扑克牌,将未排序的元素逐个插入到已排序序列的合适位置。对于部分有序的列表,插入排序效率较高。

在这三者中,​冒泡排序的算法逻辑通常被认为是最直接、最容易理解和实现的,这也是它成为经典入门算法的原因。