冒泡排序
介绍
冒泡排序(Bubble Sort)是一种基础的排序算法。它的基本思想是通过比较相邻的两个元素,将较大的元素交换到后面,从而将最大的元素冒泡到数组的末尾。这个过程重复多次,直到数组有序为止。这个算法的名字由来是因为越大的元素会经由交换慢慢 “浮” 到数组的顶端。
原理
冒泡排序的原理可以用下图来演示,假设要对数组 [5, 3, 4, 1, 2] 进行升序排序:
![image-20240314164122156](https://i-blog.csdnimg.cn/blog_migrate/d77c050f621ddb98655be6c3141bfc2a.png)
第一趟排序:
比较第 1 和第 2 个元素,5 > 3,交换位置,得到 [3, 5, 4, 1, 2]
![image-20240314164839964](https://i-blog.csdnimg.cn/blog_migrate/886333de9b4beafa32483fae10e789c0.png)
比较第 2 和第 3 个元素,5 > 4,交换位置,得到 [3, 4, 5, 1, 2]
![image-20240314165349138](https://i-blog.csdnimg.cn/blog_migrate/bd5c35116450bb58e3e73eb28797a88d.png)
比较第 3 和第 4 个元素,5 > 1,交换位置,得到 [3, 4, 1, 5, 2]
![image-20240314214930053](https://i-blog.csdnimg.cn/blog_migrate/2f247f269ca43f3e117300f8054193fc.png)
比较第 4 和第 5 个元素,5 > 2,交换位置,得到 [3, 4, 1, 2, 5]
![image-20240314215357590](https://i-blog.csdnimg.cn/blog_migrate/b6b9eea5cf1e6a07b4abff6f03010872.png)
第一趟排序结束后,最大的元素已经移到了最后一个位置。
![image-20240314215658670](https://i-blog.csdnimg.cn/blog_migrate/385a4ac20824ce231c803d3ee85650a1.png)
第二趟排序:
比较第 1 和第 2 个元素,3 < 4,不交换位置,得到 [3, 4, 1, 2, 5]
![image-20240314220157207](https://i-blog.csdnimg.cn/blog_migrate/e32a8c0f13a49f201472eabcbf546448.png)
比较第 2 和第 3 个元素,4 > 1,交换位置,得到 [3, 1, 4, 2, 5]
![image-20240314220420612](https://i-blog.csdnimg.cn/blog_migrate/da66921b51317e8a76053256f86c8768.png)
比较第 3 和第 4 个元素,4 > 2,交换位置,得到 [3, 1, 2, 4, 5]
![image-20240314221109766](https://i-blog.csdnimg.cn/blog_migrate/6bde7c985888999a3c6ee7ebb4747118.png)
比较第 4 和第 5 个元素,4 < 5,不交换位置,得到 [3, 1, 2, 4, 5]
![image-20240314221342981](https://i-blog.csdnimg.cn/blog_migrate/6003e9a439ff89f2b7c14b850b7bf1e1.png)
第二趟排序结束后,次大的元素已经移到了倒数第二个位置。
依此类推,经过 n -1 趟排序后,数组就变成了有序的。
完整的动态效果如下:
代码
冒泡排序的 Java 代码实现如下:
/**
* 冒泡排序的实现
*
* @author 孤诣
*/
public class BubbleSort {
public static void main(String[] args) {
// 数据准备
int[] dataArray = {7, 0, 9, 5, 6, 8, 4, 3, 1, 2};
System.out.println("排序前: " + Arrays.toString(dataArray));
// 排序
bubbleSort(dataArray);
System.out.println("排序后: " + Arrays.toString(dataArray));
}
/**
* 冒泡排序算法的实现
*
* @param source - 源数据
*/
public static void bubbleSort(int[] source) {
// 数组中元素的个数
int itemNum = source.length;
// 外层循环, 决定排序次数
for (int i = 0; i < itemNum; i++) {
// 内层循环, 决定比较次数
for (int j = 0; j < itemNum - 1; j++) {
if (source[j] > source[j + 1]) {
swap(source, j, (j + 1));
}
}
}
}
/**
* 将一个数组的两个元素位置互换
*
* @param source - 源数组
* @param i - 第一个元素
* @param j - 第二个元素
*/
private static void swap(int[] source, int i, int j) {
int temp = source[i];
source[i] = source[j];
source[j] = temp;
}
}
通过【原理】部分的的图解我们可以发现,在进行第 i 轮的排序时,从倒数第(i-1)位置开始,元素就已经是有序的了,而上面的代码依然会将未排序的元素与排序完成的部分进行比较,直到全部比较完成。
因此,我们可以对上面的代码进行优化,使其在排序过程中可以忽略排序完成的部分。
具体做法是记录未排序元素个数 disorderItemNum
,每轮排序只需要对数组前 disorderItemNum
个元素比较即可。每完成一轮排序,未排序元素个数将会减一。优化后的代码如下:
/**
* 冒泡排序算法的实现 - 优化
*
* @param source - 源数据
*/
public static void bubbleSort(int[] source) {
// 数组中未排序元素的个数, 这些元素总是位于已排序元素的前面
int disorderItemNum = source.length;
// 外层循环, 决定排序次数
while (disorderItemNum > 0) {
// 内层循环, 决定比较次数
for (int i = 0; i < disorderItemNum - 1; i++) {
if (source[i] > source[i + 1]) {
swap(source, i, (i + 1));
}
}
// 更新数组中未排序元素的个数
disorderItemNum--;
}
}
评估
时间复杂度:
- 最好情况:
O(n)
- 最坏情况:
O(n^2)
- 平均时间复杂度:
O(n^2)
空间复杂度:O(1)
稳定性:稳定。
补充 - 排序算法的稳定性
判断一个排序算法是否稳定的方法是检查在排序过程中相同值的元素是否保持其原有的顺序关系。具体来说,一个稳定的排序算法在排序过程中,如果两个相等的元素在数组中相邻,那么在排序之后,这两个元素仍然相邻,并且顺序不会改变。
举个例子,对数组 [3(1), 2, 4,3(2), 1] 进行排序:
稳定的排序算法:[1, 2, 3(1), 3(2), 4]
不稳定的排序算法:[1, 2, 3(2), 3(1), 4]
冒泡排序的优点是排序思想简单,代码实现容易。但是,它的效率相对较低,不适合对大数据集进行排序。在实际应用中,通常选择更高效的排序算法,如快速排序、归并排序等,这些算法,后续将会逐一介绍。