文章目录
一、排序算法概述
1.1 排序算法介绍
排序也称排序算法(Sort Algorithm), 排序是将一组数据, 依指定的顺序进行排列的过程。
1.2 排序算法的分类
常见的排序算法分类如下图所示:
【注】
-
内部排序
指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
-
外部排序
数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
二、冒泡排序
2.1 什么是冒泡排序
冒泡排序(Bubble Sorting)是交换排序的一种。它重复地遍历要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。遍历元素的工作是重复进行的,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
在排序的过程中,值较大(或较小,视排序规则而定)的元素逐渐从前移向后部, 就象水底下的气泡一样逐渐向上冒。因此称这种算法为冒泡排序算法。
2.2 冒泡排序的基本步骤
冒泡排序的基本步骤如下(假设将 N 个元素按照从小到大顺序排序):
- 首先从左到右比较相邻的两个元素,如果第一个比第二个大,就交换他们两个;
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对;
- 基于上面一轮的排序,最大的元素将会被排到最后一个位置,原序列变成了一个新的序列;
- 在新的序列中,最大的元素已经无需参与后面比较。因此只需要对前面 N-1 个元素继续执行第二轮 1-2 的排序步骤。
- 第二轮排序执行完毕,将会再次得到一个新的序列。在这个新的序列中,第二大的元素将会被排到倒数第二个位置。至此,第二大的元素和第一大的元素已经在整个序列中排好序。
- 在第二轮得到的新序列中,最大的元素和第二大的元素已经无需进行后面比较。因此只需要对前面 N-2 个元素继续执行第三轮 1-2 的排序步骤。
- 依次循环下去,直到最终没有相邻的元素需要交换,最终的到的序列就是排序完毕的序列。
【动图演示】
下面将用一个动图来演示一下对序列 [5, 3, 1, 2]
按照从小到大顺序进行冒泡排序的过程:
【分析】
从上面的动图演示的冒泡排序的过程,我们可以得到以下两个重要结论:
- n 个元素组成的序列将进行 n-1 轮排序;
- 第 i 轮相邻元素比较的次数为 n-i 次;
2.3 冒泡排序的代码实现
【案例需求】
假设待排序序列如下:
int arr[] = {
5,7,2,1,8,3};
要求将上面的序列使用冒泡排序算法按照从小到大顺序排序。
【思路分析】
冒泡排序的代码可以依据上面的两个重要结论来写实现。
首先,根据结论:第 i 轮相邻元素比较的次数为 n-i 次,则第一轮比较的次数为 n-1 次,第二轮的比较次数为 n-2 次…。
第一轮比较的代码实现如下:
int temp;
for(int j=0; j<arr.length-1; j++){
// 比较相邻元素
if(arr[j] > arr[j+1]){
// 如果前一个元素大于后一个则交换位置
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
第一轮比较的结果如下:
从上面可以看到,第一轮比较后,最大的元素到了最后的位置,第二轮比较只需要对前面 5 个元素进行即可。
第二轮比较的代码实现如下:
int temp;
for(int j=0; j<arr.length-2; j++){
// 比较相邻元素
if(arr[j] > arr[j+1]){
// 如果前一个元素大于后一个则交换位置
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
第二轮比较结果如下:
经过第二轮比较,第二个元素到了倒数第二个位置,第三轮比较只需要对前面 4 个元素进行即可。
总结:从第一轮和第二轮比较我们可以看出来,它们的代码是高度重合的,唯一不同的地方是 for
循环中的判断条件在不断变化——第一轮是 j<arr.length-1
、第二轮是 j<arr.length-2
,因此可以预见,第 i 轮将是 j<arr.length-i
。
那么根据上面的总结,我们可以将代码进行精简:即在第一轮的 for
循环外面再加一个 for
循环用于表示轮次,根据结论 “n 个元素需要比较 n-1 轮”,因此外层 for
循环需要循环 n-1 次;再根据结论 “第 i 轮相邻元素比较的次数为 n-i 次”,因此第一轮的 for
循环只需要将判断条件改为 j<n-i
即可。
【代码实现】
完整的冒泡排序算法的代码如下:
/**
* @Description 对 arr 中的元素按照从小到大进行冒泡排序
* @Param [arr]
*/
public static void bubbleSort(int[] arr){
int temp;
// N 个元素要进行 N-1 轮的排序
for (int i=1; i<arr.length; i++){
// 第 i 轮排序要进行 N-i 次的相邻元素比较
for (int j=0; j<arr.length-i; j++){
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
System.out.print("第 " + i + " 轮排序后的结果为:");
System.out.println(Arrays.toString(arr));
}
}
运行结果如下:
可以看到,原数组中的元素被按照从小到大的顺序重新排序了。
2.4 冒泡排序的优化
其实对 n 个元素组成的序列进行冒泡排序并不一定是要进行 n-1 轮排序的。
比如对于上面 2.3 节的原序列 [5, 7, 2, 1, 8, 3]
按照从小到大顺序进行冒泡排序,理论上需要 n-1=5 轮排序可以得到最终结果。但是实际上第三轮排序结束后就已经完成冒泡排序了,因此在第三轮之后没有再也没有发生过元素的交换。
所以我们可以得到一个结论:如果我们在某轮排序中,没有发生过一次元素交换,说明已经排好序了,可以提前结束冒泡排序。
因此我们可以在冒泡排序的代码中加入一个标志位用于标识本轮排序是否发生了元素的交换,如果没有发生交换,那么就可以提前结束排序程序。
优化后的冒泡排序代码如下:
/**
* @Description 优化后的排序程序 —— 对 arr 中的元素按照从小到大进行冒泡排序
* @Param [arr]
*/
pu