冒泡排序,插入排序以及选择排序浅析

一.冒泡排序

算法简述

冒泡排序法(也叫泡沫算法,鸡尾酒算法),是一种简单但运行效率最慢的排序算法,它重复地走访将要排序的元素,对两两相邻的元素进行比较,若发现其顺序与我们将要排列的顺序不符则将这两个元素交换位置,经过每一轮的比较和交换后一个最小的元素会浮(被交换)到排序元素的首位,这也是该算法名字的由来。

算法运作

  1. 对两个元素进行比较,若前者大于后者,则将这两个元素交换
  2. 从右至左对数组中两两相邻的元素执行1中的操作
  3. 经过步骤2的过程后最小的数浮到了排序元素的首位
  4. 除去步骤3中的最小数,对剩下的元素执行步骤2,3,4中的操作,直到排序元素只剩一个

Java实现代码

    //交换数组中两个指定位置的元素
    public static void swap(int[] a,int i,int j){
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }

    //冒泡排序,改进版
    public static void bubbleSort(int[] a){
        //使用一个标志量,一旦发现在一整轮的比较中无交换元素
        //则表明其后的元素已经排好序了
        boolean flag = true;
        for(int i=0;i<a.length-1&&flag;i++){
            flag = false;
            for(int j=a.length-1;j>i;j--){
                if(a[j]<a[j-1]){
                    swap(a, j-1, j);
                    //若是有交换元素,则将flag赋值为true
                    //表明下一轮(如果有的话)需要继续进行
                    flag = true;
                }
            }
        }
    }

算法复杂度分析

  1. 最好情况下:由于设置了一个标志量,当数组已经按从小到大的顺序排列,那么比较(n-1)次后就退出了循环,因而算法复杂度为:O(n)。
  2. 平均情况和最差情况: 最差情况是数组按从大到小的顺序排列,需要比较和交换的次数均为:n*(n-1)/2,算法复杂度为:O(n2),而平均情况时间代价为最差情况的一半,那么其交换次数为:(n-1)n/4,而比较次数不变仍为:n(n-1)/2,其时间复杂度仍然为:O(n2)。

二.选择排序

算法简述

选择排序法,是一种简单直观的算法,它通过在排序的元素中选择最小的元素放在排序元素的第一位,一步一步的缩小范围使得数组有序。

算法运作

  1. 从待排序的元素中选择出最小的元素与排序元素第一位交换
  2. 排除刚才找出的最小元素,将剩下的元素归为待排序的元素,循环执行步骤1,2直到待排序的元素个数为1

Java实现代码

    public static void selectSort(int[] a){
        for(int i=0;i<a.length-1;i++){
            //使用一个临时量记录当前排序元素中的最小值的下标
            int min = i;
            for(int j=i+1;j<a.length;j++){
                //如果有元素小于临时量记录对应的值,则更新临时量
                if(a[j]<a[min]){
                    min = j;
                }
            }
            //最小值和排序元素的第一位交换
            swap(a,min,i);
        }
    }

算法复杂度分析

无论是最好,平均还是最差情况,其比较次数均为(n-1)*n/2,交换次数为:(n-1),因此时间复杂度为:O(n2)。

三.插入排序

算法简述

插入排序,从字面上可以理解为,将待元素插入到已排序的元素中,即找到待元素在已排序元素中的正确位置,相信大家都有玩过欢乐斗地主,当我们收到牌后,一般情况下都会依次将待排序的牌插入到前面已经排好序的牌组中,这个过程就是我们插入排序的思想。

算法运作

  1. 从数组第二位开始,依次选择对应位置的元素为待排序元素执行步骤2,直到可选择的元素为空
  2. 将待排序元素与其前一位元素进行比较,如果前者小于后者,那么交换这两个元素,循环执行步骤2至待排序元素大于或等于前一位元素

Java实现代码

    public static void insertSort(int[] a){
        int i,j;
        //从第二位开始到最后一位
        for(i=1;i<a.length;i++){
            //内循环和冒泡排序类似,也是需要进行两两比较,只不过这
            //里是循环到待排序元素大于或等于前一位元素就终止
            //然后进入下一轮(如果有的话)
            for(j=i;j>0&&a[j-1]>a[j];j--){
                swap(a, j-1, j);
            }
        }
    }

算法复杂度分析

  1. 最好情况下其比较次数为(n-1),交换次数为0,时间复杂度为:O(n)。
  2. 平均情况和最差情况:最差情况比较次数为(n-1)*n/2(即1+2+…+(n-1)),而一般认为平均情况的时间代价为最差情况时间代价的一半,即(n-1)*n/4,因此这两种情况的时间复杂度均为O(n2)。

三种算法的比较和总结

编写如下测试代码

    public static void main(String... args){
        //产生100000个随机数来测试排序算法
        int[] a = new int[100000];
        int[] b = new int[100000];
        int[] c = new int[100000];
        Random random = new Random();
        for(int i=0;i<a.length;i++){
            a[i] = random.nextInt(100000);
            c[i] = b[i] = a[i];
        }
        Date date1 = new Date();
        bubbleSort(a);
        Date date2 = new Date();
        System.out.println("冒泡排序花费的时间为:"+(date2.getTime()-date1.getTime())+"ms");

        date1 = new Date();
        selectSort(b);
        date2 = new Date();
        System.out.println("选择排序花费的时间为:"+(date2.getTime()-date1.getTime())+"ms");

        date1 = new Date();
        insertSort(c);
        date2 = new Date();
        System.out.println("插入排序花费的时间为:"+(date2.getTime()-date1.getTime())+"ms");
    }

测试结果如下

冒泡排序花费的时间为:21982ms
选择排序花费的时间为:3941ms
插入排序花费的时间为:5598ms

测试结果分析

从测试结果很明显地看出,尽管这三种算法在平均情况下的时间复杂度均为:O(n2),但冒泡排序所花费时间要明显多于选择排序和插入排序,而选择排序花费时间又要少于插入排序算法。因而从性能上来说,选择排序>插入排序>冒泡排序。

三种排序算法时间复杂度总结如下表

最好情况平均情况最差情况
冒泡排序O(n)O(n2)O(n2)
插入排序O(n)O(n2)O(n2)
选择排序O(n2)O(n2)O(n2)
点赞