zoukankan      html  css  js  c++  java
  • 冒泡排序算法分析

    工作这么久了,由于本人非科班出身,对于一些基础的算法理解一直不是很透彻。以冒泡算法为例,每次复习后,过段时间总是遗忘,又要重新看,今天索性静下心来详细分析一下,虽然是最基础的算法,然而小算法中未必没有大智慧,供本人及后来人参考。

    先来看一个最笨的排序:

        public static void sort1(int[] a){
            int count = 0 ;
            for(int i=0; i<a.length; i++){
                for(int j=0; j<a.length; j++){
                    count++ ;if(a[i] < a[j]){
                        int temp = a[i];
                        a[i] = a[j] ;
                        a[j] = temp ;
                    }
                }
            }
            System.out.println("#sort1 forloop count - " + count);
        }

      这是一种比较笨的排序方法,很多新人在写排序的时候,可能这样写理解会比较直观一些,将数组循环length*length次,所有值俩俩进行一次对比,最后得出结论:

        public static void main(String[] args) {
            int[] arr = new int[]{12,4,54,57,87,3,41,1,3,4,1,3,4,31,2} ;
            sort1(arr);
            System.out.println("最终结果:" + Arrays.toString(arr));
        }

    结果:

    #sort1 forloop count - 225
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    可见本次执行225次循环;

    再来看一个升级版:

        public static void sort2(int[] a){
            int count = 0 ;
            for(int i=0; i<a.length; i++){
                for(int j=0; j<a.length-1; j++){
                    count++ ;
                    if(a[j] > a[j+1]){
                        int temp = a[j] ;
                        a[j] = a[j+1] ;
                        a[j+1] = temp ;
                    }
                }
            }
            System.out.println("#sort2 forloop count - " + count);
        }

    本方法对上一个方法进行了进一步的简化,第一层同样循环length次,而第二层循环了length-1次,同时比较只存在于第二层循环中,由于第二层的比较重,将当前下标与当前下标+1进行比较,所以总的循环数需要length-1,否则会造成数组越界,这一种算法比较常见,可能某些培训机构对学生进行培训时也是使用这种排序算法,本方法对第二层的循环进行了-1操作,总排序次数当然要少于第一种。

    执行:

    int[] arr = new int[]{12,4,54,57,87,3,41,1,3,4,1,3,4,31,2} ;
    sort2(arr);
    System.out.println("最终结果:" + Arrays.toString(arr));

    结果:

    #sort2 forloop count - 210
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    本次执行210次循环,排序结果同第一种方法,效率有所提升。

    对于大学没有学过算法和数据结构相关课程,又是初入开发行业的童鞋来说,本种算法可能需要分析一下才能明白原理。

    我们来分析一下第二种方法的缺陷。

    先看第二层循环:

    每次循环,都要将第N个数与第N+1个数进行比较,如果第N个数大,则互换位置。例如:

    数组:{12,4,54,3,22,53}

    第一次循环,j=0,则比较12与4的大小,发现12>4,则互换12与4的位置,结果如下:

    {4,12,54,3,22,53}

    第二次循环,j=1,此次比较12与54的大小,发现12<54,则保持不动,结果如下:

    {4,12,54,3,22,53}

    第三次循环,j=2,比较54和3大小,54>3,则互换,结果如下:

    {4,12,3,54,22,53}

    第四次循环,j=3,比较54和22大小,54>22,互换,结果如下:

    {4,12,3,22,54,53}

    最后一次循环,j=4,比较54和53大小,54>53,互换,结果如下:

    {4,12,3,22,53,54}

    我们发现,每一次 i 循环,都可以将arr[length-1-i]这个位置的数选举而出,也就是说,整个循环只需要length-1次(最后一轮不用排序,因为剩下的最后一个数肯定是最小值),即可完成所有数的排序,所以第二个方法可进行进一步优化:

        public static void sort2_up(int[] a){
            int count = 0 ;
            for(int i=0; i<a.length-1; i++){
                for(int j=0; j<a.length-1; j++){
                    count++ ;
                    if(a[j] > a[j+1]){
                        int temp = 0 ;
                        temp = a[j] ;
                        a[j] = a[j+1] ;
                        a[j+1] = temp ;
                    }
                }
            }
            System.out.println("#sort2_up forloop count - " + count);
        }

    执行:

            int[] arr = new int[]{12,4,54,57,87,3,41,1,3,4,1,3,4,31,2} ;
            sort2_up(arr);
            System.out.println("最终结果:" + Arrays.toString(arr));

    结果:

    #sort2_up forloop count - 196
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    那么现在我们来看一下冒泡排序。

    以上方法,第一层循环length-1次已经没有问题,那么来看下第二层循环。

    每一次我们比较第j个元素和第j+1个元素,但是仔细分析,其实还是有无效比较,我们先把sort2_up中的每一次外层循环结果打印一下看看过程:

        public static void sort2_up(int[] a){
            int count = 0 ;
            for(int i=0; i<a.length-1; i++){
                for(int j=0; j<a.length-1; j++){
                    count++ ;
                    if(a[j] > a[j+1]){
                        int temp = 0 ;
                        temp = a[j] ;
                        a[j] = a[j+1] ;
                        a[j+1] = temp ;
                    }
                }
                System.out.println("#sort2_up i="+i+", result: " + Arrays.toString(a));
            }
            System.out.println("#sort2_up forloop count - " + count);
        }
    #sort2_up i=0, result: [4, 12, 54, 57, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 87]
    #sort2_up i=1, result: [4, 12, 54, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 57, 87]
    #sort2_up i=2, result: [4, 12, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 54, 57, 87]
    #sort2_up i=3, result: [4, 3, 12, 1, 3, 4, 1, 3, 4, 31, 2, 41, 54, 57, 87]
    #sort2_up i=4, result: [3, 4, 1, 3, 4, 1, 3, 4, 12, 2, 31, 41, 54, 57, 87]
    #sort2_up i=5, result: [3, 1, 3, 4, 1, 3, 4, 4, 2, 12, 31, 41, 54, 57, 87]
    #sort2_up i=6, result: [1, 3, 3, 1, 3, 4, 4, 2, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=7, result: [1, 3, 1, 3, 3, 4, 2, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=8, result: [1, 1, 3, 3, 3, 2, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=9, result: [1, 1, 3, 3, 2, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=10, result: [1, 1, 3, 2, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=11, result: [1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=12, result: [1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up i=13, result: [1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #sort2_up forloop count - 196
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    那么我们来看下内层循环,也就是j层循环:

    每一次对第j个元素和第j+1个元素进行比较,循环length-1次。

    问题出在循环length-1次上,为什么呢,因为从结果我们可以看到,其实每一轮都已经选出了一个最大值,比如第i=0次循环,选出了一个最大值,第i=1次循环,选出了除上一次最大值之外的最大值,以此类推。

    也就是说,j层循环中,每次循环,后面的比较完全是多余的,多比较了多少次呢?答案是 i 次。

    第一轮循环,也就是i=0,此时需要在内层比较所有元素大小,最终选举出一个最大值;当第二轮循环时,i=1,最大值已经选举出,此时已没有必要再进行最后一轮j循环,j循环层只需要执行length-1-i次即可,以此类推。

    这就是冒泡循环的思想,每一轮循环,冒泡选出一个最大值,放到末尾:

        public static void bubbleSort(int[] arr) {
            int count = 0 ;
            System.out.println("待排序数组:" + Arrays.toString(arr));
            for (int i=0;i<arr.length-1;i++) {
                for (int j=0;j<arr.length-1-i;j++) {
                    count++ ;
                    if (arr[j]>arr[j+1]) {
                        int temp = arr[j] ;
                        arr[j] = arr[j+1] ;
                        arr[j+1] = temp ;
                    }
                }
                System.out.println("第" + (i+1) + "次排序结果:" + Arrays.toString(arr));
            }
            System.out.println("#bubbleSort forloop count - " + count);
        }

    执行一下:

            int[] arr = new int[]{12,4,54,57,87,3,41,1,3,4,1,3,4,31,2} ;
            bubbleSort(arr);
            System.out.println("最终结果:" + Arrays.toString(arr));

    结果:

    待排序数组:[12, 4, 54, 57, 87, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2]
    #第1次排序结果:[4, 12, 54, 57, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 87]
    #第2次排序结果:[4, 12, 54, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 57, 87]
    #第3次排序结果:[4, 12, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 54, 57, 87]
    #第4次排序结果:[4, 3, 12, 1, 3, 4, 1, 3, 4, 31, 2, 41, 54, 57, 87]
    #第5次排序结果:[3, 4, 1, 3, 4, 1, 3, 4, 12, 2, 31, 41, 54, 57, 87]
    #第6次排序结果:[3, 1, 3, 4, 1, 3, 4, 4, 2, 12, 31, 41, 54, 57, 87]
    #第7次排序结果:[1, 3, 3, 1, 3, 4, 4, 2, 4, 12, 31, 41, 54, 57, 87]
    #第8次排序结果:[1, 3, 1, 3, 3, 4, 2, 4, 4, 12, 31, 41, 54, 57, 87]
    #第9次排序结果:[1, 1, 3, 3, 3, 2, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第10次排序结果:[1, 1, 3, 3, 2, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第11次排序结果:[1, 1, 3, 2, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第12次排序结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第13次排序结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第14次排序结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #bubbleSort forloop count - 105
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    冒泡排序的思想,说直白点就是每次冒泡选出最大值,并且每次选出的最大值不参与下次排序。

    冒泡排序的进一步优化:

    上面的冒泡排序算法确实已经很高效了,但是我们仔细看输出结果,第12次、13次、14次,其排序结果相同,也就是说,实际到第12次排序已经完成,13、14次为无效操作。我们加个标记,对过程进行进一步优化:

        public static void bubbleSort_up(int[] arr) {
            int count = 0 ;
            System.out.println("待排序数组:" + Arrays.toString(arr));
            for (int i=0;i<arr.length-1;i++) {
                boolean isComplete = true ;
                for (int j=0;j<arr.length-1-i;j++) {
                    count++ ;
                    if (arr[j]>arr[j+1]) {
                        int temp = arr[j] ;
                        arr[j] = arr[j+1] ;
                        arr[j+1] = temp ;
                        if (isComplete)
                            isComplete = false ;
                    }
                }
                System.out.println("#第" + (i+1) + "次排序结果:" + Arrays.toString(arr));
                if (isComplete)
                    break;
            }
            System.out.println("#bubbleSort_up forloop count - " + count);
        }

    我们每一次外层循环开始时,记录一个完成排序标记,如果内层循环有变动,则标记为false,否则为true,看结果:

    待排序数组:[12, 4, 54, 57, 87, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2]
    #第1次排序结果:[4, 12, 54, 57, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 87]
    #第2次排序结果:[4, 12, 54, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 57, 87]
    #第3次排序结果:[4, 12, 3, 41, 1, 3, 4, 1, 3, 4, 31, 2, 54, 57, 87]
    #第4次排序结果:[4, 3, 12, 1, 3, 4, 1, 3, 4, 31, 2, 41, 54, 57, 87]
    #第5次排序结果:[3, 4, 1, 3, 4, 1, 3, 4, 12, 2, 31, 41, 54, 57, 87]
    #第6次排序结果:[3, 1, 3, 4, 1, 3, 4, 4, 2, 12, 31, 41, 54, 57, 87]
    #第7次排序结果:[1, 3, 3, 1, 3, 4, 4, 2, 4, 12, 31, 41, 54, 57, 87]
    #第8次排序结果:[1, 3, 1, 3, 3, 4, 2, 4, 4, 12, 31, 41, 54, 57, 87]
    #第9次排序结果:[1, 1, 3, 3, 3, 2, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第10次排序结果:[1, 1, 3, 3, 2, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第11次排序结果:[1, 1, 3, 2, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第12次排序结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #第13次排序结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]
    #bubbleSort_up forloop count - 104
    最终结果:[1, 1, 2, 3, 3, 3, 4, 4, 4, 12, 31, 41, 54, 57, 87]

    少一次,这是因为我们这一组数字只少排序了最后一个循环,最后一次j=length-1-i,只有一次排序操作,我们换一组数来看效果:

    执行:

            int[] arr = new int[]{12,4,54,57,87,3,39,40,41,42,43,44,45,46,47} ;
            bubbleSort_up(arr);
            System.out.println("最终结果:" + Arrays.toString(arr));

    结果:

    待排序数组:[12, 4, 54, 57, 87, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47]
    #第1次排序结果:[4, 12, 54, 57, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 87]
    #第2次排序结果:[4, 12, 54, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 57, 87]
    #第3次排序结果:[4, 12, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第4次排序结果:[4, 3, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第5次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第6次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #bubbleSort_up forloop count - 69
    最终结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]

    再来看未优化的冒泡排序:

    执行:

            int[] arr = new int[]{12,4,54,57,87,3,39,40,41,42,43,44,45,46,47} ;
            bubbleSort(arr);
            System.out.println("最终结果:" + Arrays.toString(arr));

    结果:

    待排序数组:[12, 4, 54, 57, 87, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47]
    #第1次排序结果:[4, 12, 54, 57, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 87]
    #第2次排序结果:[4, 12, 54, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 57, 87]
    #第3次排序结果:[4, 12, 3, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第4次排序结果:[4, 3, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第5次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第6次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第7次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第8次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第9次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第10次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第11次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第12次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第13次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #第14次排序结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]
    #bubbleSort forloop count - 105
    最终结果:[3, 4, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 54, 57, 87]

    未优化情况下,多排序了36次。

    优化后的冒泡排序方法仍然会有一轮无效排序,如果有更好的思路,欢迎留言指正。

    优化版的冒泡排序,数组越长、越有序,效率越高。

    以上。

  • 相关阅读:
    使用匿名内部类的创建形式完成线程的2种方式
    线程的2种创建方式
    深入学习 viewport 和 px
    <script> 标签位置应该放在哪里?
    JavaScript 创建对象的几种方式?
    Palindrome Number
    String To Integer
    Reverse Integer
    Zigzag Conversion
    Longest Palindromic Substring
  • 原文地址:https://www.cnblogs.com/gaorongyi/p/10396830.html
Copyright © 2011-2022 走看看