Bootstrap

冒泡排序(结合动画进行可视化分析)

写在前面的话

首先声明强调所有视频均来自于网站:https://visualgo.net/zh ,这是一个通过动画可视化数据结构和算法的网站,非常有助于大家理解和学习数据结构和算法

冒泡排序是一种简单的排序算法,采用重复遍历待排序的数列,比较相邻元素,并将其顺序错乱的元素交换过来,直到没有需要交换的元素为止。因其在每一轮中都将当前未排序部分中的最大(或最小)的元素"冒泡"到数列的一端而得名。

冒泡排序的基本过程

  1. 比较相邻元素:从数组的第一个元素开始,比较当前元素与下一个元素。如果它们的顺序错误(例如,在升序排序中,当前元素大于下一个元素),则交换它们。
  2. 重复遍历:对每一对相邻元素执行上述操作,直到整个数组没有需要交换的元素为止。遍历的次数通常是数组的长度减一。
  3. 优化:如果在某一轮遍历中没有进行任何交换操作,则说明数组已经排序完成,可以提前终止算法。

伪代码

以下是冒泡排序的伪代码示例:

function bubbleSort(arr)  
    n = length(arr)                 // 获取数组长度  
    for i from 0 to n-1 do          // 外循环,控制轮数  
        swapped = false              // 初始化交换标志为 false  
        for j from 0 to n-i-2 do    // 内循环,比较相邻元素  
            if arr[j] > arr[j+1] then  
                swap(arr[j], arr[j+1]) // 交换元素  
                swapped = true       // 发生交换,标记为 true  
            end if  
        end for  
        if not swapped then          // 如果没有发生交换  
            break                    // 提前结束排序  
        end if  
    end for  
end function

冒泡排序是一种基础的排序算法,其主要思想是通过相邻元素的比较和交换,将未排序部分的最大(或最小)元素逐步移动到已排序部分的边界。该算法的名字来源于其工作方式,就像气泡一样,较大的元素逐渐“冒泡”到数组的顶部。

外层循环的范围 0 to n-1
  • 目的:外层循环的目的是控制排序的轮数。由于每完成一轮,最大的元素就会被“冒泡”到数组的末尾,因此在每一轮中,我们可以减少比较的次数。
  • 具体解释:外层循环从 0n-1,实际上只需要执行 n-1 次,因为在第 n-1 次遍历时,所有元素都已经有序,不再需要进行比较。
内循环的范围 0 to n-i-2
  • 原因:内循环的范围是 0n-i-2,这是因为:
    • 每完成一轮外循环,最大的元素会被放到数组的最后位置。因此,下一轮排序时,最后一个元素不需要再参与比较。
    • n-i-1 是最后一个元素的索引,而 n-i-2 是倒数第二个元素的索引。因此,内循环只需比较到 n-i-2 以确保不会越界。
    • (网络上也常常有关于0 ton-1-j的范围叙述)
表达式的详细解释
n-i-2
  • 解释
    • 在数组中,外循环的变量 i 表示当前轮数。当 i=0 时,我们比较整个数组,直到 n-1
    • i=1 时,我们只需要比较到 n-2,因为 n-1 位置上的元素在上一次比较中已确定是当前最大元素(已经排序)。
    • 因此,n-i-2 是,需要比较的最后一个元素的索引(不包括 n-1 位置,即最大的已被排好序的元素)。
n-1-j
  • 解释
    • 这个表达式表示的是当前内循环正在比较的元素的索引的反向表示。
    • j0 开始增加时,n-1-j 则从 n-1 降到 n-2,表示未排序部分最后一个元素的位置。
    • j 达到 n-i-2 时,n-1-j 就是数组的最后一个索引。
1. 范围的动态变化
  • n-i-2 是用来确定内循环的上限,随着 i 的增加,比较的次数减少,反映已经确定的位置。
  • n-1-j 则是记录了内循环当前所比较的最后元素,从大到小推进。
2. 等价性
  • j 增加并且达到其最大值 n-i-2,此时 n-1-j 会反映出数组底部已确立的位置,简化了不再需要比较的过程。

  • 视角不同n-i-2 是从未排序部分考虑的上限,而 n-1-j 则是从已排序部分和遍历过程中考虑的当前元素位置。

  • 有效性:这两个表达式共同决定着当前需要比较的元素,维护算法逻辑上的有效性,同时保证只有必要的元素被比较,避免不必要的重复。

swapped 变量的使用
  • 作用swapped 变量用于记录在当前轮次内是否发生了元素的交换。如果在某一轮中没有任何交换,说明数组已经是有序的,可以提前结束排序,从而提高算法效率。
  • 初始化:在每一轮开始时,将 swapped 初始化为 false。如果在内循环中发生了交换,则将其设为 true
  • 提前结束:在内循环结束后,检查 swapped 的值。如果仍为 false,则说明没有发生任何交换,数组已经排序完成,直接跳出外循环。

冒泡排序

实例解析

假设我们有一个数组 [5, 3, 8, 4, 2],我们使用冒泡排序对其进行升序排序。

第一步:初始数组
[5, 3, 8, 4, 2]
第一轮排序
  • 比较 5 和 3,5 > 3,交换:[3, 5, 8, 4, 2]
  • 比较 5 和 8,5 < 8,不交换。
  • 比较 8 和 4,8 > 4,交换:[3, 5, 4, 8, 2]
  • 比较 8 和 2,8 > 2,交换:[3, 5, 4, 2, 8]

当前第一轮结束,最大元素 8 已"冒泡"到数组的最终位置。

第二轮排序
[3, 5, 4, 2, 8]
  • 比较 3 和 5,3 < 5,不交换。
  • 比较 5 和 4,5 > 4,交换:[3, 4, 5, 2, 8]
  • 比较 5 和 2,5 > 2,交换:[3, 4, 2, 5, 8]

第二轮结束,5 已在正确位置。

第三轮排序
[3, 4, 2, 5, 8]
  • 比较 3 和 4,3 < 4,不交换。
  • 比较 4 和 2,4 > 2,交换:[3, 2, 4, 5, 8]

第三轮结束。

第四轮排序
[3, 2, 4, 5, 8]
  • 比较 3 和 2,3 > 2,交换:[2, 3, 4, 5, 8]

到此为止,数组已经完全有序,排序结束。

时间复杂度

冒泡排序的时间复杂度为:

  • 最坏情况:O(n^2)(当数组完全逆序时)
  • 平均情况:O(n^2)
  • 最好情况:O(n)(当数组已经有序时,优化后可提前结束)

虽然冒泡排序实现简单,且对小规模数据表现良好,但由于其较慢的性能,常用于教学和演示排序算法的基础。

;