冒泡排序算法之解析及优化(一看就懂)

      前言       

面试中经常遇到写个排序算法,相信大家都会写冒泡排序算法,虽然很多人会写,我敢肯定,不是所有人都深知这个算法深层次的逻辑和规律,因为对于一个普通的开发者来说,通常工作中绝大部分时间用不到这些内容,为了应付面试,临时把这个算法背下来,一般面试官一看写对了,也不会再问什么,如果想要研究下算法问题,我们就有必要彻底弄清楚其中的奥秘,这些是基础,

 

一、概要

        它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

         简单讲就是:将一组大小的杂乱的元素,逐个比较两个相邻的元素,较大或较小的放在右边,最后达到升序或降序的排序

二、基本原理

      

  1. 比较相邻的元素。如果第一个比第二个大(或小),就交换他们两个。 

  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 

  3. 针对所有的元素重复以上的步骤,除了最后一个。 

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

三、算法分析

      算法复杂度:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次

    最好时间复杂度为O(N)

    Cmax = N(N-1)/2 = O(N2)
    Mmax = 3N(N-1)/2 = O(N2)
    冒泡排序的最坏时间复杂度为O(N2)
    冒泡排序的平均时间复杂度为O(N2)

        算法稳定性 : 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,    如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候      也  不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法

四、代码实现

       以java代码为例

    public static void bubbleSort2(int[] list) {
        for(int i = 0 ; i< list.length-1 ; i++) {
            for(int j= 0 ; j < list.length-1-i ; j++) {
                System.out.println("==========外循环 i====" + i + "==========内循环 j====" + j);
                System.out.println(" 换位前: "  + Arrays.toString(list));
                System.out.println(" 比较的元素为:第" + j + " 和 " +(j+1) );
                if(list[j]>list[j+1]) {
                    int temp = list[j];
                    list[j]=list[j+1];
                    list[j+1]=temp;
                }
                System.out.println(" 换位后: "  + Arrays.toString(list) );
            }
        }
    }

打印日志为:

==========外循环 i====0==========内循环 j====0
 换位前: [9, 2, 5, 1]
 比较的元素为:第0 和 1
 换位后: [2, 9, 5, 1]
==========外循环 i====0==========内循环 j====1
 换位前: [2, 9, 5, 1]
 比较的元素为:第1 和 2
 换位后: [2, 5, 9, 1]
==========外循环 i====0==========内循环 j====2
 换位前: [2, 5, 9, 1]
 比较的元素为:第2 和 3
 换位后: [2, 5, 1, 9]
==========外循环 i====1==========内循环 j====0
 换位前: [2, 5, 1, 9]
 比较的元素为:第0 和 1
 换位后: [2, 5, 1, 9]
==========外循环 i====1==========内循环 j====1
 换位前: [2, 5, 1, 9]
 比较的元素为:第1 和 2
 换位后: [2, 1, 5, 9]
==========外循环 i====2==========内循环 j====0
 换位前: [2, 1, 5, 9]
 比较的元素为:第0 和 1
 换位后: [1, 2, 5, 9]

    

 分析上述代码可以看出,

       总共两层循环,内层循环次数随着外层次数的增加而减少,为什么要这样设计呢?

       原因分析:

        根据算法规律,每次排序都能把数组内的最大或最小元素放在数组的最顶端,以上述代码为例,上述是以从小到大排序,第一次外循环结束就能把最大的放在最右边,所以在内循环中可以看到,互相比较元素的下标随着外循环的增加而增加,比较的次数也就越来越小,最后直接完排序,

  分析日志可以看出

     第一次外循环结束,已经把最大的元素推到最右边了,后续的比较,已经不再不需要这个元素参与了

总结:如果希望元素从小到大排序,第一次外循环结束就能把最大元素放至最右边,第二次外循环线束就能把第二大的元素放至右二的位置,以此内推,直至排序结束

 

五、算法优化

     具体代码如下

        

    public static void bubbleSort2(int[] list) {
        boolean bChange = false; // 交换标志
        for(int i = 0 ; i< list.length-1 ; i++) {
            bChange = false;
            for(int j= 0 ; j < list.length-1-i ; j++) {
                System.out.println("==========外循环 i====" + i + "==========内循环 j====" + j);
                System.out.println(" 换位前: "  + Arrays.toString(list));
                System.out.println(" 比较的元素为:第" + j + " 和 " +(j+1) );
                if(list[j]>list[j+1]) {
                    int temp = list[j];
                    list[j]=list[j+1];
                    list[j+1]=temp;
                    bChange = true;
                }
                System.out.println(" 换位后: "  + Arrays.toString(list) );
            }
            // 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序
            if (false == bChange)
                break;
        }
    }

 

 增加交换标志,可以结束循环, 内循环如果没有交换,说明已经是有序数列,直接跳出大循环,提高了效率。

 

 

Guess you like

Origin blog.csdn.net/yb546822612/article/details/106015296