文章汇总归纳整理于:算法竞赛学习之路[Java版]
- 冒泡排序是交换排序中的一种
- 所谓交换,是指根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
默认排序后的数据,从小到大进行排列
冒泡排序的基本思想
- 从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列比较完。
- 我们称这次为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置(或将最大的元素交换到待排序列的最后一个位置),关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”(或关键字最大的元素如石头一般下沉至水底)。
- 下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素(或最大元素)放到了序列的最终位置……
- 这样最多做 n-1 趟冒泡就能把所有元素排好序。
冒泡排序的过程
- 执行到第四趟冒泡时,未排序数据只剩一个,无需再继续执行冒泡,即已经完成排序
冒泡排序的代码实现
这里以 P1116 车厢重组 为例,实现冒泡排序
// package 冒泡排序; import java.io.*; public class Main { // 快读快写 static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))); public static void main(String[] args) { // 读取数据 int n = readInt(); int[] nums = new int[n]; // 存放初始的车厢顺序的数组 // 读取初始的车厢顺序 for ( int i=0; i<n; i++ ) { nums[i] = readInt(); } int ans = 0; // 记录交换的次数 // 冒泡排序 for ( int i=1; i<n; i++ ) { // 最多 n-1 趟冒泡 for ( int j=n-1; j>=i; j-- ) { // 从后向前遍历,将未排序数据中最小的数冒泡到最上面 if ( nums[j] < nums[j-1] ) { // 逆序,交换数据 ans++; // 交换次数++ // 交换数据 int t = nums[j]; nums[j] = nums[j-1]; nums[j-1] = t; } } } // for ( int i=0; i<n; i++ ) out.println(nums[i]); out.print(ans); // 输出结果 out.flush(); } // 读取整数 static int readInt() { try { in.nextToken(); } catch (IOException e) { e.printStackTrace(); } return (int) in.nval; } }
冒泡排序的优化
- 在冒泡排序过程中,如果存在某趟的冒泡中,没有发生数据的交换,则说明在本趟冒泡排序之前,数据已经有序了,无需再进行后面的排序。
- 因此我们可以利用这个对冒泡排序进行优化,在每趟排序之前,创建一个标记变量,用于记录本趟是否发生数据交换,如果没有发生数据交换,则无需再进行后续的排序,直接结束排序。
// package 冒泡排序; import java.io.*; public class Main { static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))); public static void main(String[] args) { // 读取数据 int n = readInt(); int[] nums = new int[n]; // 存放初始的车厢顺序的数组 for ( int i=0; i<n; i++ ) { nums[i] = readInt(); } int ans = 0; // 记录交换的次数 // 冒泡排序 for ( int i=1; i<n; i++ ) { // 最多 n-1 趟冒泡 // 标记本趟是否有发生数据的交换,如果没有则说明数据已经有序,无需后序的排序 boolean flag = false; for ( int j=n-1; j>=i; j-- ) { // 将未排序数据中最小的数冒泡到最上面 if ( nums[j] < nums[j-1] ) { // 逆序,交换数据 ans++; // 交换次数++ // 交换数据 int t = nums[j]; nums[j] = nums[j-1]; nums[j-1] = t; flag = true; // 本趟冒泡有发生数据的交换 } } if ( !flag ) break; // 如果本趟未发生数据交换则说明数据已经有序,无需后序的排序 } // for ( int i=0; i<n; i++ ) out.println(nums[i]); out.print(ans); out.flush(); } static int readInt() { try { in.nextToken(); } catch (IOException e) { e.printStackTrace(); } return (int) in.nval; } }
冒泡排序的性能分析
- 空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
- 时间效率:
- 当初始序列有序时,显然第一趟冒泡后 flag 依然为 false (本趟没有元素交换),从而直接跳出循环,比较次数为 n-1,移动次数为0,从而最好情况下的时间复杂度为O(n)
- 当初始序列为逆序时,需要进行 n-1 趟排序,第 i 趟排序要进行 n-i 次关键字的比较,而且每次比较后都必须交换元素位置。这种情况下,
- 从而,最坏情况下的时间复杂度为O(n2),平均时间复杂度为O(n2)。
- 没有优化的冒泡排序的平均时间复杂度为O(n2)
- 稳定性:由于 i>j 且 A[i]=A[j] 时,不会发生交换,因此冒泡排序是一种稳定的排序方法。