问题
给定n个整数,将它们按照(非降)序排序。
观察
排序算法有很多种,今天就介绍一个简洁且容易理解的算法——冒泡排序
熟悉规则
接下来观察以下两张图片,熟悉一下游戏规则。
- 这张图片告诉我们:有序序列中,任意一对相邻元素有序。
来看另一张图片
- 这张图片告诉我们:无序序列中,总有一对相邻元素无序。
我们要做的
我们要做的是 依次比较每一对相邻元素,若逆序,交换之。
若整趟都没有进行交换,则排序完成。否则,再做一趟。
Java实现
简单实现
private void bubblesort1(int a[], int n) {
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n - i - 1; j ++) {
if (a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
助记码
i∈[0,N-1) //循环N-1遍
j∈[0,N-1-i) //每遍循环要处理的无序部分
swap(j,j+1) //两两排序(升序/降序)
伪代码
function bubble_sort (array, length) {
var i, j;
for(i from 0 to length-1){
for(j from 0 to length-1-i){
if (array[j] > array[j+1])
swap(array[j], array[j+1])
}
}
}
优化
优化1
也许你也发现了,上面的两个算法都有两个for循环,也就是说是O(n^{2})时间复杂度,即使对于[1, 2 , 3, 4 , 5 , 6 ]这样的数组输入,也需要比较N^2次,那么能不能针对这一点进行优化呢?优化的结果,使对于上述那样的良性数组输入,可以节省点比较次数。你看,我下面这段代码,有没有达到这个目的?
private void bubbleSort3(int A[], int n) { //输入数组A,其中有n个元素
System.out.println("游戏开始咯");
boolean sorted = false; //sorted为整体有序标志,我们首先假定数组 未排序
while ( !sorted ) { //设置了一个while循环,当sorted为true时,不进入循环,即排序完成.
//那么,何时sorted为true,又该怎么设置呢? 别急,往下看
sorted = true; //这里怎么为true了?毫无道理,这么突兀?实际上,这里只是假定已经排序,
// 所以设置为true只是个假设,因为下面可能要清除这个true标志
for (int i = 1; i < n; i ++) { //对数组进行一趟(只能保证进行一趟)循环遍历,但是while外循环保证进行多趟,直到排序完成
if (A[i-1] > A[i]) {
//交换
int temp = A[i-1];
A[i-1] = A[i];
A[i] = temp;
sorted = false;//若发现了局部无序,就算局部调整过来了,那谁知道整体是不是有序的,所以清除有序标志。
}
}
n = n - 1; //排序规模递减,因为每进行一个for循环,则有一个最大的数排到最后,它已经找到自己的位置,不需要再排序,也不需要比较啦
}
}
另外呢,神游之间发现上述代码的精简版
private void bubbleSort4(int A[], int n) {
for (boolean sorted = false; sorted = !sorted; n--) {
for (int i = 1; i < n; i ++) {
if (A[i-1] > A[i]) {
int temp = A[i-1];
A[i-1] = A[i];
A[i] = temp;
sorted = false;
}
}
}
}
优化2
////改进思路:记录一轮下来标记的最后位置,下次从头部遍历到这个位置就Ok
public static void bubbleSort2(int [] num, int length) {
int last = length;
int k;
while (last > 0) {
k = last;
last = 0;
for (int j = 1; j < k; j ++) {
if (num[j - 1] > num[j]) {
int temp = num[j - 1];
num[j - 1] = num[j];
num[j] = temp;
last = j;
}
}
}
}
算法正确性的证明
问题: 该算法是否一定会终止(有穷性)? 至少迭代多少趟呢?
分析
我们知道:
该算法具有
- 不变性。经k轮扫描交换,最大的k个元素必然会就位。
单调性。经k轮扫描交换,问题规模缩减为n-k;
通过这两条性质的挖掘综合,则得出算法的正确性,经过至少n趟扫描后,算法必然终止, 且给出正确答案