排序算法之冒泡法、选择法、快速排序

前言

对一组数据进行排序在我们日常编程的过程中经常用到排序算法。下面就经常用到的冒泡法、选择法还有快速排序法进行一下回顾。

 

关于时间复杂的一个计算方法:(简单版)

步骤:

1、找到执行次数最多的语句

2、语句执行语句的数量级

3、用O表示结果

然后:

1、用常数1取代运行时间中的所有加法常数

2、在修改后的运行次数函数中,只保留最高阶项

3、如果最高阶项存在且不是1,那么我们就去除于这个项相乘的常数。比如3n^2我们取n^2

最后就可以得到你们想要的结果了。

 

一、冒泡法

分析:

冒泡法是把两个相邻的元素做比较,(以从小到大排列为例),如果两个相邻的元素中,前一个元素比后一个元素大,那么需要交换一下两个元素的位置。依次执行此操作,直到把最大的元素放到最后的位置。再从头开始循环执行(注:已经放到后面,排好位置的不用再做比较)

具体实现:

private static void method1(int[] arr){
        for (int i=0;i<arr.length-1;i++){
            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;
                }
            }
        }

}

我们来看冒泡法的时间复杂度:

冒泡排序一共要进行(n-1)次循环,每一次循环都要进行当前n-1次比较,所以一共的比较次数是:
(n-1) + (n-2) + (n-3) + … + 1 = n*(n-1)/2;

相当于是:(n-1)+(n-2)+(n-3)+...+(n-(n-1))=(n-1)*n-(1+2+3+...+(n-1))=(n-1)*n-((n-1+1)*(n-1)/2)=(n-1)*n-(n*(n-1)/2)=n*(n-1)/2=n^2-2n

根据我们的步骤走,保留最高次项,去掉相乘的常数就可以得到时间复杂度为:O(n^2),所以冒泡排序的时间复杂度是 O(n^2)。

 

接下来我们说一种冒泡法的升级版,把代码做一些改动,来增加冒泡法的效率

具体实现如下:

/**
     * 冒泡法的升级版
     * @param arr
     */
    public static void bubbleSort(int arr[]) {
        boolean didSwap;
        for (int i=0;i<arr.length-1;i++){
            didSwap=false;

            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;

                    didSwap = true;
                }
            }

            if(didSwap == false)
                return;
        }
    }

由代码可知,我们相对于第一种冒泡法,增加了一个变量didSwap,那么为什么只增加了这么一个变量就能提高冒泡法的工作效率?

看第一层for循环,在第一层for循环里,初始化didSwap=false;第二层for循环是用来给相邻的两个元素做比较,如果满足条件,则调换两个相邻元素的位置,并且把disSwap变量置为true。反过来想,如果经历过第二层for循环之后,那么这个didSwap变量依然为false。说明在第二层for循环的时候,没有满足arr[j]>arr[j+1]的条件,进而说明此时,整个数组中的元素已经是从小到大排好的顺序了,无需再进行调换,所以也就无需再进行元素的比较,所以这时,跳出所有的循环:

if(didSwap == false)
                return;

所以,我们知道了,通过这一个简单的变量,实现了冒泡法效率的提升。

二、选择法

分析:选择法是从第一个元素开始,拿出当前位置的数据和后面的数据做比较,(以从小到大排列为例)如果当前位置的数据比后面数据大,那么交换两个数据的位置。接着往下比较,直到和最后一个元素比较完,那么第一轮轮训比较结束,这时第一个位置得到的数据为所有数据中最小的一个。  紧接着开始第二轮轮训:从第二个元素开始,仿照第一次轮训,依次和后面的元素进行比较,直到和最后一个元素比较完为止。第三次轮训。。。。。。。直到所有轮训都结束,那么得到的这组数据就是从小到大排列好的数据。

具体实现:

private static void method2(int[] arr){
        for (int i=0;i<arr.length-1;i++){
            for (int j=i+1;j<arr.length;j++){
                if (arr[j]<arr[i]){
                    int temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
        }
}

我们来看选择法的时间复杂度:

(n-1)+(n-2)+(n-3)+...+(n-(n-1))=(n-1)*n-(1+2+3+...+(n-1))=(n-1)*n-((n-1+1)*(n-1)/2)=(n-1)*n-(n*(n-1)/2)=n*(n-1)/2=n^2-2n

时间复杂度同冒泡法一样也是:O(n^2)。

三、快速排序算法

分析:快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

具体实现:

private static void quickSort(int[] arr,int low,int high ){
        int i,j,temp,t;
        if (low>high){
            return;
        }
        i=low;
        j=high;
        //temp是基准位
        temp=arr[low];
        while(i<j){
            //先看右边,依次往左递减
            while(temp<=arr[j]&&i<j){
                j--;
            }

            //再看左边,依次往右递增
            while(temp>=arr[i]&&i<j){
                i++;
            }

            //如果满足条件则交换
            if (i<j){
                t=arr[i];
                arr[i]=arr[j];
                arr[j]=t;
            }

        }

        //最后将基准为与i和j相等位置的数字交换
        arr[low]=arr[i];
        arr[i]=temp;

        //递归调用左半数组
        quickSort(arr,low,i-1);

        //递归调用右半数组
        quickSort(arr,i+1,high);

    }

我们来看一看快速排序算法的时间复杂度:

快速排序的一次划分算法从两头交替搜索,直到low和high重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。

理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。

最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n2)。

因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。

 

注意:常用的时间复杂度所耗费的时间从小到大依次是:

O(1 )< O(logn) < O(n) < O(n*logn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

总结:由上可以看出正常情况下来说,快速排序的速度是要大于冒泡排序和选择排序的。

四、插入排序算法

插入排序实际上是从数组的第二个数(即索引为1的数)开始,与前面的每一个数从右到左依次做比较,如果这个数比前面的某个数小,那么互换两个数的位置,具体实现如下:

    /**
     * 插入排序
     */
    public void insertSort(int[] arr){
        for (int i=1;i<arr.length;i++){
            for (int j=i;j>0;j--){
                if (arr[j]<arr[j-1]){
                    int temp=arr[j];
                    arr[j]=arr[j-1];
                    arr[j-1]=temp;
                }
            }
        }
    }

下面借用某大神的一张图,来展示常见的几种排序方式的时间复杂度:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章