凡人能看懂的冒泡排序和快速排序
这篇文章将主要介绍两种非常普遍的排序方式:冒泡排序和快速排序。内容包括两种排序的原理,代码剖析,以及时间复杂度分析。
因为注意到很多快排的文章在介绍完原理之后并没有去解释这么做的原因,以及没有从宏观上去看代码所实现的功能,也就是没有很详细的说明这么写代码的原因,看完之后会产生一些疑问,所以我打算以个人的浅见去解释以上的问题。由于本人是一个普通人,水平有限,所以有任何质疑或者想法,欢迎留言。
那下面我们也以扑克牌来引入这个排序问题吧~
假设有一堆乱序的扑克牌,如果是你,你打算如何将这些乱序的扑克牌变成正序呢?
冒泡排序的思路:
从第二个扑克开始和它前面相邻的扑克比较,比它大则不动,比它小交换位置。这样我们在第一轮排序中可以得到最大的扑克牌,因为最大值是一直在向后传递,直到传到最后,那个值必然是扑克牌中最大的值。
(柱形图比扑克更直观点,第一次自己做的gif水平有限)
可以看到,最后一个值是最大的值。通过不断的循环这一操作,我们总是可以把第二大,第三大...第n大的值放到正确的位置,直到所有的排序全部正确。
冒泡排序示例代码1:
public class BubbleSortTest1 {
public static void main(String[] args) {
int[] arr = new int[]{-12,3,2,3,5,8,1};
//冒泡排序
//i循环:进行i次排序动作
for(int i = 0;i < arr.length-1;i++){
//内部j循环:进行内部排序,每次将目前最大值放置正确位置
for(int j = 0;j <arr.length-1-i;j++){
if(arr[j] >arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//遍历
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]+"\t");
}
}
}
冒泡排序复杂度:
上面的代码我们可以看到嵌套循环了两次,最好的情况是整个数组是有序的,不用排序,那么它的最优复杂度=n-1+n-2+.....+(n-(n-1))=n^2-(1+n-1)(n-1)/2=n*(n-1)/2也就是n^2,但是肯定有同学有疑问,为什么有的资料说他的最优时间复杂度为O(n)? 其实示例1的代码有可以优化的地方,聪明或者傻乎乎的你发现了吗?当内部循环排序没有做置换操作的时候,其实就说明,整个序列是有序的,无需再继续循环下去,这个时候可以直接退出程序得到结果。我们可以通过一个标志位来标识是否进行了置换操作。
改进的冒泡排序示例代码2:
public class BubbleSortTest2{
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6};
//用于判断是否发生置换操作
boolean flag;
//i循环:进行i次排序动作
for(int i = 0;i < arr.length-1;i++){
flag = false;
//内部j循环:进行内部排序,每次将目前最大值放置正确位置
for(int j = 0;j <arr.length-1-i;j++){
if(arr[j] >arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true; //有发生置换,就置为true
}
}
if(!flag) break;
}
//遍历
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]+"\t");
}
}
}
这个时候最优时间复杂度为O(n),最差时间复杂度除了上述的n*(n-1)/2比较次数外,还需要计算置换次数,每次置换次数=3,所以总的最差时间复杂度=3*n(n-1)/2,为O(n^2),所以平均复杂度=[1+2+......n*(n-1)/2]/(n*(n-1)/2)=O(n^2)
冒泡排序效率略低,我们来看一看面试问的很多,应用用的很广,人如其名的快速排序。
快速排序的思路:
随机选择一个基准,比如第一个数,将其他数和这个数做比较,比他小则放在基准左边,比他大则放到基准右边。然后对基准左边的数和基准右边的数重复上面的动作。这么说,有些抽象,我们还是来看一下动态图。Talk is cheap,我们把上述思路转成代码。
快速排序代码示例代码:
import java.util.Arrays;
public class QuickSort {
public static void quicksort(int[] arr, int low, int high){
//key为基准
int i,j,key;
//低位指针
i= low;
//高位指针
j =high;
//递归迭代时,判断是否还要进行排序。
if(i<j){
key=arr[i];
//排序终止条件
while(i<j){
//说明高位数字位置正确
while(arr[j]>=key && j>i)j--;
//如果跳出循环,则说明比key小的数值处在高位,需要置换到低位,此时低位正好是key的值,相当于是个空位
arr[i]=arr[j];
//低位数字位置正确
while(arr[i]<=key && i<j)i++;
//如果跳出循环,则说明比key大的数值处在低位,需要置换到高位
arr[j]=arr[i];
//然后再从高位指针开始新的循环比较,直到两个指针相遇则一轮比较结束
}
//此时key处于正确位置
arr[i]=key;
quicksort(arr,low,i-1);
quicksort(arr,i+1,high);
}
}
public static void main(String[] args) {
int[] arr={6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
quicksort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
快速排序复杂度:
快排需要注意指针越界还有递归能终止的问题。快速排序最好情况是每次都恰好可以对半分,一次递归共需比较n次,递归深度为lgn,复杂度为nlgn(2为底),最差情况为已排序数组,每次的排序次数=n+n-1+n-2+.....1=n(n+1)/2~1/2n^2,平均复杂度最终结果=O(nlgn),这个证明有点复杂,这里不做赘述(主要是因为水平有限)。
Bye.