01-冒泡排序

2019-11-28  本文已影响0人  ducktobey

认识排序

什么叫排序?例如有下面的一串无序数字

其实,排序的应用无处不在,例如,汽车销售根据销量排序

再例如,游戏充值,根据金额进行排序

认识了排序以后,接下来交接一下经典的10大排序算法

以上表格中的结论是基于数组进行排序的一般性结论

其中:

冒泡,选择,插入,归并,快速,希尔,堆排序属于比较排序(Comparison Sorting)

冒泡排序

接下来,先介绍一个冒泡排序,在这里所有的排序,统一以升序为例子。其冒泡排序的流程为

  1. 从头开始比较每一对相邻元素,如果第一个比第二个大,就交换它们的位置。
    • 执行完一轮后,最末尾的哪个元素就是最大元素
  2. 忽略1中曾经找到的最大元素,重复执行步骤1,直到全部元素有序

所以,通过上面的步骤,就可以得到以下的代码

int[] array = {10,9,19,28,37,56,34};
for (int end = array.length - 1; end > 0; end--) {
    for (int begin = 1; begin <= end ; begin++) {
        if (array[begin] < array[begin - 1]) {
            int tmp = array[begin];
            array[begin] = array[begin - 1];
            array[begin - 1] = tmp;
        }
    }
}
冒泡排序-优化1

通过上面这种代码,虽然实现了对元素的排序,但是还是有优化的地方,比如在某个时间节点,所有元素已经完全有序[下图],则可以提前终止冒泡排序。通过上面这种实现方式,是不能达到这种要求的。

由于在排序过程中,是通过一个元素一个元素扫描,来判断是否交换的,所以在第一轮扫描时,就可以知道该组元素是否已经有序。所以只要 if (array[begin] < array[begin - 1])成立,这说明当前组元素不完全有序,需要交换,否则就是所有元素都不用交换,则表示已经完全有序。所以可以通过下面方式进行优化

int[] array = {10,9,19,28,37,56,34};
for (int end = array.length - 1; end > 0; end--) {
    boolean sorted = true;
    for (int begin = 1; begin <= end ; begin++) {
        if (array[begin] < array[begin - 1]) {
            int tmp = array[begin];
            array[begin] = array[begin - 1];
            array[begin - 1] = tmp;
            sorted = false;
        }
    }
    if (sorted == true) {
        break;
    }
}

通过这种优化,就可以提高效率。

但是这种优化,真的可以提高效率吗?为了测试真的可以提升效率,可以进行下面的测试。

  1. 通过工具类,生成两组相同的数据
  2. 计算两种不同方式,处理相同数据所消耗的时间

所以在这里随机生成了10000个随机数,然后通过两个不同的函数来计算最终所消耗的时间,函数如下

public static void bubbleSort1(Integer[] array) {
    for (int end = array.length - 1; end > 0; end--) {
        for (int begin = 1; begin <= end ; begin++) {
            if (array[begin] < array[begin - 1]) {
                int tmp = array[begin];
                array[begin] = array[begin - 1];
                array[begin - 1] = tmp;
            }
        }
    }
}
public static void bubbleSort2(Integer[] array) {
    for (int end = array.length - 1; end > 0; end--) {
        boolean sorted = true;
        for (int begin = 1; begin <= end ; begin++) {
            if (array[begin] < array[begin - 1]) {
                int tmp = array[begin];
                array[begin] = array[begin - 1];
                array[begin - 1] = tmp;
                sorted = false;
            }
        }
        if (sorted == true) {
            break;
        }
    }

得到的结果如下

通过测试时间,得到的结果,发现没有优化的代码,所消耗的时间更短。是不是觉得很奇怪,为什么优化后,反而更慢呢?那想一个问题,bubbleSort2之所以成为优化,在什么情况下,才能被优化。应该是经过冒泡排序后,数组中的数据提前排好序的情况下,可以被优化。假设冒泡排序的数据,数据量很大,而且有很随机的话,很难达到提前有序的情况。由于bubbleSort2相对于bubbleSort1多做了一些事情,所以所消耗的时间更长。

但是如果现在的数据是升序的,同样为10000个元素,最终得到的结果为

通过结果可以发现,bubbleSort1无论数据是否提前有序,都要消耗很长的时间。但是bubbleSort2却可以大大的提高效率。不过需要注意的是,这种优化在某种前提下有效。

冒泡排序-优化2

通过上面这种优化,提前有序的概念很低,那有没有更好的优化方案呢?是有的,可以这样做:

如果序列尾部已经局部有序,可以记录最后一次交换的位置,较少比较次数。例如现在得到的数据是这样的

如果按照普通的冒泡排序算法,则是依次扫描所有的元素并进行比较。并不会理会后面数据是否有规律。但是上面这组数据,是有优化空间的,通过观察发现,使用冒泡排序的话, 最后面的几个元素的顺序是不会发生改变的,因为这些元素都已经有序,并且比前面的元素都大。也就意味着,交换操作,只需要堆前面未排序的元素进行就行了,没必要对后面已经有序的元素再次比较。所以,如果可以提前发现局部排序的元素,是可以提高效率的。所以可以这样实现

public static void bubbleSort3(Integer[] array) {
    for (int end = array.length - 1; end > 0; end--) {
        int sortedIndex = 1;//该值在数组完全女友许的时候有用
        for (int begin = 1; begin <= end ; begin++) {
            if (array[begin] < array[begin - 1]) {
                int tmp = array[begin];
                array[begin] = array[begin - 1];
                array[begin - 1] = tmp;
                sortedIndex = begin;
            }
        }
        end = sortedIndex;
    }
}

通过这种方式优化后,现在来比较三种排序算法最终的结果。同样假设数据样本为10000个升序数据,最终得到的比较结果为

可以看到,最后两种算法的效率是非常高的。

接下来,对数据进行优化,现在利用工具生成10000个数据,其中8000个数据已经排好序。最终运行程序,得到的结果为

可以看到,通过这种局部排序的数据,bubbleSort3是明显优于bubbleSort1和bubbleSort2的。

通过这种优化

  1. 最坏,平均时间复杂度为:O(n^2)
  2. 最好时间复杂度:O(n)
  3. 空间复杂度为O(1)

排序算法的稳定性(Stability)

如果相等的两个元素,在排序前后的相对位置保持不变,那么这是稳定的排序算法。例如,下列数据中,有两个值相等的元素

排序前:5,1,3(a),4,7,3(b)

稳定的排序:1,3(a),3(b),4,5,7

不稳定的排序:1,3(b),3(a),4,5,7

为什么要这样认为呢?因为在实际开发中,存在对自定义对象进行排序的情况,稳定性会影响最终的排序效果。

所以根据这个结论,可以知道冒泡排序是属于稳定的排序算法。但是,稍有不慎,稳定的排序算法也能被写成不稳定的排序算法,比如下面的冒泡排序代码就是不稳定的

public static void bubbleSort4(Integer[] array) {
    for (int end = array.length - 1; end > 0; end--) {
        for (int begin = 1; begin <= end ; begin++) {
            if (array[begin] <= array[begin - 1]) {
                int tmp = array[begin];
                array[begin] = array[begin - 1];
                array[begin - 1] = tmp;
            }
        }
    }
}

所以在写排序算法时,需要特别的注意。

原地算法(In-place Algorithm)

什么叫原地算法?

解读:

  1. 不依赖额外的资源或者依赖少数的额外资源就意味着空间复杂度低;
  2. 仅依靠输出来覆盖输入表示可以利用传进来的参数直接进行操作,不会利用其它资源

所以,空间复杂度为O(1)的算法,都可以认为是原地算法。

非原地算法,成为Not-in-place或者Out-of-place

前面的冒泡排序,属于In-place

demo下载地址

完!

上一篇下一篇

猜你喜欢

热点阅读