查看原文
其他

这几个经典的基础排序算法,你还记得吗?

倪升武 程序员私房菜 2018-12-04


阅读本文大概需要5分钟

本文我们主要来回忆一下几个基础的排序算法:冒泡排序、选择排序和插入排序。已经成为大牛的你,还记得当初这几个经典的排序算法吗?

1. 冒泡排序

冒泡排序算法运行起来非常慢,但在概念上它是排序算法中最简单的,因此冒泡排序算法在刚开始研究排序技术时是一个非常好的算法。冒泡排序算法的基本流程是:每一轮从头开始两两比较,将较大的项放在较小项的右边,这样每轮下来保证该轮最大的数在最右边。

算法程序如下:

public void bubbleSort(int[] source) {
    for(int i = source.length - 1; i > 0; i--) {
        for(int j = 0; j < i; j++) {
            if(a[j] > a[j+1])
                swap(source, j, j+1); //交换,具体实现略
        }
    }
}


冒泡排序算法还有个可以改进的地方,就是在算法中加入一个布尔变量标识该轮有没有进行数据的交换,若在某一次排序中未发现气泡位置的交换,则说明待排序的无序区中所有的项均已满足排序后的结果。

public void bubbleSort(int[] source) {
   boolean exchange;
   for(int i = source.length - 1; i > 0; i--) {
       exchange = false;
       for(int j = 0; j < i; j++) {
           if(a[j] > a[j+1]) {
               swap(source, j, j+1);
             exchange = true;
           }
       }
       if(!exchange) return;
   }
}


算法分析:冒泡排序最好的情况是初始状态是正序的,一次扫描即可完成排序,所以最好的时间复杂度为 O(N);最坏的情况是反序的,此时最坏的时间复杂度为 O(N^2)。平均情况,每轮 N/2 次循环,N 轮时间复杂度为 O(N^2)。

算法是稳定的,因为当 a=b 时,由于只有大于才做交换,故 a 和 b 的位置没有机会交换,所以算法稳定。

空间复杂度为 O(1),不需要额外空间。

2. 选择排序

选择排序改进了冒泡排序,将必要的交换次数从 O(n^2) 减少到 O(n),但是比较次数仍保持为 O(n^2)。冒泡排序每比较一次就可能交换一次,但是选择排序是将一轮比较完后,把最小的放到最前的位置(或者把最大的放到最后)。选择排序为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。(一般在 java 中不是这种情况,因为 java 中只是改变了引用位置,而实际对象的位置并没有发生改变。)

public void selectSort(int[] source) {  
    int min;  
    for(int i = 0; i < source.length; i++) {  
        min = i;  
        for(int j = i + 1; j < source.length; j++) {  
            if(a[j] < a[min])  
                min = j;    
        }  
        swap(i, min);  
    }  
}


算法分析:选择排序最好和最坏的情况一样运行了 O(N^2) 时间,但是选择排序无疑更快,因为它进行的交换少得多,当 N 值较小时,特别是如果交换时间比比较时间大得多时,选择排序实际上是相当快的。平均复杂度也是 O(N^2)。

算法是不稳定的,假设 a=b,且 a 在 b 前面,而某轮循环中最小值在 b 后面,而次最小值需要跟 a 交换,这时候 b 就在 a 前面了,所以选择排序是不稳定的。

空间复杂度为 O(1),不需要额外的空间。

3. 插入排序

插入排序的实现步骤为:从第一个元素开始,该元素可以认为已经被排序 -> 取出下一个元素,在已经排序的元素序列中从后向前扫描 -> 如果该元素小于前一个元素,则将两者调换,再与前一个元素比较–> 重复第三步,直到找到已排序的元素小于或者等于新元素的位置 -> 将新元素插入到该位置中 -> 重复第二步

public void insertSort(int[] source) {
    for(int i = 1; i < source.length; i++) {
        for(int j = i; (j > 0) && (source[j] < source[j-1]); j--) {
            swap(j, j-1);
        }
    }
}


算法分析:插入排序最好的情况是序列已经是升序排列了,在这种情况下,需要进行 N-1 次比较即可,时间复杂度为 O(N),最坏的情况是序列降序排列,这时候时间复杂度为 O(N^2)。因此插入排序不适合对于数据量比较大的排序应用。但是如果需要排序的数据量很小(如小于千),那么插入排序还是一个不错的选择。插入排序平均时间复杂度为 O(N^2),但是它要比冒泡排序快一倍,比选择排序还要快一点,经常被用在较复杂的排序算法的最后阶段,例如快速排序。

算法是稳定的,假设 a=b,且 a 在 b 的前面,其排序位置必然比 b 先确定,而后面再插入 b 时,必然在 a 的后面,所以是稳定的。

空间复杂度为 O(1),不需要额外的空间。

以上三种排序是排序算法中比较基础的,也相对容易理解,就回忆到这里。

更多推荐阅读:

微服务架构盛行的时代,你需要了解点 Spring Boot

说点特别的:这个公众号简单的分析

Java干货视频资源

web前端干货视频资源

资源大放送!!!永久有效,持续更新(必收藏系列)

【整理分享】你需要的资源在这里,拿好不谢

关注“程序员私房菜”,学习更多技术干货,领取更多免费资源

↓↓↓

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存