一、算法概述
冒泡排序是一种稳定的原地排序算法,属于交换类排序的范畴。其核心特性是"相邻比较、逐步冒泡":每一轮排序都会对序列中未排序的相邻元素进行两两比较,若发现前一个元素大于后一个元素(升序排序),则交换两者的位置;经过一轮排序后,序列中未排序部分的最大值会被"冒泡"到未排序部分的末尾,成为已排序部分的第一个元素。重复执行该过程,直到整个序列无需交换即可完成排序,即序列已完全有序。
二、核心原理与排序流程
冒泡排序的核心逻辑简单易懂,本质是通过多轮次的相邻元素比较与交换,逐步将最大值(或最小值)移动到对应位置,缩小未排序区域的范围,直至整个序列有序。以下以升序排序为例,详细拆解其核心原理与完整排序流程,降序排序仅需调整比较逻辑即可。
1. 核心思想
冒泡排序的核心思想可概括为"多轮遍历、逐泡归位",具体可拆解为两个核心步骤,形成循环闭环:
-
单轮遍历:对序列中未排序的相邻元素进行两两比较,若前一个元素大于后一个元素,则交换两者位置;遍历结束后,未排序区域的最大值会被"冒泡"到未排序区域的末尾,成为已排序区域的一部分。
-
循环终止:重复执行单轮遍历操作,每轮遍历后未排序区域的长度减少1(因为最大值已归位);当某一轮遍历过程中未发生任何一次元素交换时,说明序列已完全有序,无需继续遍历,排序终止。
2. 排序流程(示例演示)
为了更直观地理解冒泡排序的流程,我们以一个具体的无序序列$$[5, 3, 8, 4, 2]$$(升序排序)为例,详细演示每一轮的排序过程:
-
初始序列: [5,3,8,4,2] ,未排序区域为整个序列 [0,4] (索引范围); 比较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归位到索引4(序列末尾),未排序区域变为 [0,3] ,发生4次比较、3次交换。
-
第二轮序列: [3,5,4,2,8] ,未排序区域 [0,3] ; 比较3和5:3<5,不交换; 比较5和4:5>4,交换,序列变为 [3,4,5,2,8] ; 比较5和2:5>2,交换,序列变为 [3,4,2,5,8] ; 第二轮结束:第二大值5归位到索引3,未排序区域变为 [0,2] ,发生3次比较、2次交换。
-
第三轮序列: [3,4,2,5,8] ,未排序区域 [0,2] ; 比较3和4:3<4,不交换; 比较4和2:4>2,交换,序列变为 [3,2,4,5,8] ; 第三轮结束:第三大值4归位到索引2,未排序区域变为 [0,1] ,发生2次比较、1次交换。
-
第四轮序列: [3,2,4,5,8] ,未排序区域 [0,1] ; 比较3和2:3>2,交换,序列变为 [2,3,4,5,8] ; 第四轮结束:第四大值3归位到索引1,未排序区域变为 [0,0] (长度为1,已有序),发生1次比较、1次交换。
-
第五轮检查:未排序区域 [0,0] ,无需遍历;或直接遍历已排序序列,未发生任何交换,排序终止。
最终排序结果:[2, 3, 4, 5, 8]。通过该示例可见,冒泡排序的核心是"每轮归位一个最大值",逐步缩小未排序区域,直至序列完全有序。
三、算法实现(以Java为例)
冒泡排序的实现逻辑简单,分为基础实现和优化实现两种。基础实现适用于理解算法原理,优化实现可大幅提升算法在有序或接近有序序列中的效率,避免无效遍历。以下分别给出两种实现的代码示例,并附带详细注释,便于读者理解和调试。
1. 基础实现(未优化)
java
/**
* 冒泡排序基础实现(未优化,升序排序)
* @param arr 待排序数组
*/
public static void bubbleSortBasic(int[] arr) {
// 边界校验:数组为空或长度小于2,无需排序,直接返回
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
// 外层循环:控制排序轮次,共执行n-1轮(每轮归位一个最大值)
for (int i = 0; i < n - 1; i++) {
// 内层循环:控制每轮的比较次数,每轮比上一轮少1次(已归位的元素无需再比较)
// 每轮比较范围:0到n-1-i(n-1-i为当前未排序区域的末尾索引)
for (int j = 0; j < n - 1 - i; j++) {
// 相邻元素比较:若前一个元素大于后一个元素,交换两者位置
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
/**
* 辅助方法:交换数组中两个索引位置的元素
* @param arr 待交换元素所在的数组
* @param i 第一个元素的索引
* @param j 第二个元素的索引
*/
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2. 优化实现(标志位优化)
基础实现的核心问题的是:当序列已完全有序或接近有序时,仍会执行n-1轮遍历,造成大量无效操作。优化实现通过添加一个"交换标志位",用于标记当前轮次是否发生了元素交换:若某一轮遍历未发生任何交换,说明序列已完全有序,可直接终止排序,无需继续执行后续轮次,从而将最好时间复杂度优化为O(n)。
java
/**
* 冒泡排序优化实现(标志位优化,升序排序)
* 核心优化:添加交换标志位,避免有序序列的无效遍历
* @param arr 待排序数组
*/
public static void bubbleSortOptimized(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
// 交换标志位:true表示当前轮次发生了交换,false表示未发生交换(序列已有序)
boolean swapped;
// 外层循环:控制排序轮次,最多执行n-1轮
for (int i = 0; i < n - 1; i++) {
swapped = false; // 每轮开始前,初始化标志位为false
// 内层循环:控制每轮的比较次数
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
swapped = true; // 发生交换,将标志位置为true
}
}
// 若当前轮次未发生任何交换,说明序列已完全有序,直接终止循环
if (!swapped) {
break;
}
}
}
3. 进阶优化(双向冒泡排序)
除了标志位优化,还有一种进阶优化方式------双向冒泡排序(又称鸡尾酒排序)。其核心思想是:传统冒泡排序仅能从左到右"冒泡"最大值,双向冒泡排序则交替从左到右"冒泡"最大值、从右到左"冒泡"最小值,进一步缩小未排序区域的范围,减少比较次数。这种优化适用于序列中存在少量较小值位于序列末尾、少量较大值位于序列前端的场景,可提升排序效率。
java
/**
* 冒泡排序进阶优化(双向冒泡排序/鸡尾酒排序,升序排序)
* 核心优化:交替从左到右、从右到左冒泡,减少无效比较
* @param arr 待排序数组
*/
public static void bubbleSortBidirectional(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
boolean swapped; // 交换标志位
int left = 0; // 未排序区域左边界
int right = n - 1; // 未排序区域右边界
while (left < right) {
swapped = false;
// 第一趟:从左到右,冒泡出未排序区域的最大值,归位到right
for (int j = left; j < right; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
swapped = true;
}
}
right--; // 最大值归位,右边界左移
if (!swapped) {
break; // 未发生交换,序列有序,终止循环
}
swapped = false;
// 第二趟:从右到左,冒泡出未排序区域的最小值,归位到left
for (int j = right; j > left; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr, j, j - 1);
swapped = true;
}
}
left++; // 最小值归位,左边界右移
if (!swapped) {
break;
}
}
}