小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
突然的一个想法,实现一个"优雅系列",覆盖的范围就是我们常见的几个算法,昨天算是一个开篇,已经实现了二分法查找,今天来看一看冒泡排序,后面还会有选择排序,插入排序,快速排序,希望这个系列可以让大家对常见的算法有一个简单而又清晰的认识。不要怂,就是干。
话不多说,让我们一起看看冒泡排序吧。
前提
给定一个非空数组 a,要求把 a 变成一个升序排列的数组。
算法描述
- 依次比较数组中相邻的两个元素,若 a[j] > a[j+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是让最大的元素排至最后
- 重复以上步骤,直到整个数组有序
算法的描述挺简单的哈,但是实现起来确有很多坑,我们先来看看最基础的实现,然后才想着优化。
算法实现基础版
上面提到一轮冒泡结束之后就会得到一个最大的值。先来实现一轮的比较
for(int j = 0; j < a.length - 1; j ++) {
if(a[j] > a[j+1]) {
swap(a, j, j+1);
}
}
复制代码
我们需要比较多少轮呢?数组有多少个元素就比较多少轮。再添加一个外层循环即可。比较简单对吧,我们看一下基础版的实现。
private static void bubble(int[] a){
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a.length - 1; j++) {
if(a[j] > a[j+1]) {
swap(a, j, j+1);
}
}
}
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
复制代码
这里内层循环控制的是每轮的比较(一共比较了 a.length - 1 次,后面就是记作 n),外层控制的是多少个元素的冒泡。这里应该可以发现一个小小的问题了,是不是每一个元素都需要比较 n 次呢,显然是不需要的,第一次冒泡的时候已经把最大的元素放在了最后,第二轮比较的时候只需要比较 n - 1 次即可,依次类推。我们就可以这样优化内层的比较。
for (int j = 0; j < a.length - 1 - i; j++) {
if(a[j] > a[j+1]) {
swap(a, j, j+1);
}
}
复制代码
上面的优化还比较好理解,我们再思考一下如何优化,优化无非就两个方面,一个是减少冒泡的次数,另一个就是减少每次冒泡的比较次数。
现在思考一个极端的场景,给定的数组就是有序的,此时会出现什么情况呢?外层一直在循环,内层其实一次 swap 都没有调用,但是,问题是,如果在第一轮的两两比较中都没有发生交换,其实数组就已经是有序的了,我们不需要外层一直循环了。
算法实现优化版
private static void bubble(int[] a) {
for (int i = 0; i < a.length; i++) {
boolean swapped = false;
for (int j = 0; j < a.length - 1 - i; j++) {
if(a[j] > a[j+1]) {
swap(a, j, j+1);
swapped = true;
}
}
if(!swapped) {
break;
}
}
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
复制代码
到这里看起来已经非常棒了,上面我们已经对冒泡的次数,以及每次冒泡需要比较的次数都做了优化,但这就够了嘛,不 我们还可以对比较的次数做进一步的优化,每轮冒泡时,最后一次交换的索引可以作为下一轮冒泡比较的次数,如果这个值是 0, 就证明整个数组有序,直接退出外层循环即可。
换句话说,我们要不要继续冒泡,取决内层的循环,如果一次都没有交换,那就表明数组有序,直接退出即可,如果内层循环只交换了一次,那么这个交换的位置就是我们下一次冒泡需要比较的次数,如果交换了两次,那我们就取后面的位置作为下一次冒泡比较的次数,不知道这样的解释大家能不能听得懂。
举个例子吧,a = [5,3,1,9] 第一次冒泡之后的结果:3159, 最后交换的位置 j 等于 2 下一次冒泡时只需要比较 2 次即可。我理解这个也花了一会儿,但是仔细想想还是有点感觉的,因为我上一轮若是没有交换,就说明后面的都是有序的,自然下一轮也不需要再去比较了。上一轮冒泡比较到哪里,下一轮的冒泡就比较到那里即可。上一轮冒泡没有比较,证明数组有序,直接退出。
算法实现终极版
private static void bubble(int[] a) {
int loopCount = a.length - 1;
while(true) {
int lastIndex = 0;
for (int j = 0; j < loopCount; j++) {
if(a[j] > a[j+1]) {
swap(a, j, j+1);
lastIndex = j;
}
}
if(lastIndex == 0) {
break;
}
}
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
复制代码
综上,希望我已经把冒泡排序说的清清楚楚了,如果你有更好的解法,欢迎留言讨论。