最熟悉的几种排序——冒泡排序插入排序和选择排序

Posted 一个程序员的取经之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最熟悉的几种排序——冒泡排序插入排序和选择排序相关的知识,希望对你有一定的参考价值。

 ——几种时间复杂度为O(n)的排序算法

内容目录

1. 如何分析一个排序算法2. 冒泡排序2.1 冒泡排序2.2冒泡排序的平均时间复杂度3.插入排序3.1 插入排序4. 选择排序4.1 选择排序5. 冒泡排序和插入排序

1. 如何分析一个排序算法

一般来说,分析一个算法我们从执行效率上来分析。常见的有复杂度分析法,从时间复杂度和空间复杂度两个方面来看。

  • 时间复杂度分为最好时间复杂度、最坏时间复杂度和平均时间复杂度。

  • 实际的开发过程中,数据规模可能不是很大,所以,在分析时间复杂度的时候,系数、低阶和常量有时候也不能忽视。

  • 在排序过程中,涉及到的操作有两个,比较和移动,所以要把这两个操作的复杂度都要考虑进去。

  • 空间复杂度用内存消耗来度量。空间复杂度为O(1)的排序算法,称为原地排序算法。

  • 排序的数据中,如果相等的数据在排序前后位置不发生改变,能减少移动次数。这种不改变相同数据前后位置的特性,称之为稳定性。算法是否具有稳定性,也是一个分析指标。

2. 冒泡排序

2.1 冒泡排序

每次比较相邻两个数据,如果不满足大小关系就进行交换操作。每一次冒泡,都能保证一个数据移动到它对应的位置。比如5个数据排序,第一次冒泡操作,依次比较1和2,2和3,3和4,4和5,5和6位置的数据,如果不满足大小关系就交换两个元素。

如果某次冒泡无需移动元素了,说明已经达到有序了,无需继续操作。

public int[] bubbleSort(int[] array) {
    if (array.length <= 0) {
        return null;
    }
    for (int i = 0; i < array.length; i++) {
        boolean flag = false;
        for (int j = 0; j < array.length - j - 1; j++) {
            int temp;
            if (array[j] > array[j + 1]) {
                temp = array[j + 1];
                array[j + 1] = array[j];
                array[j] = temp;
                flag = true;
            }
        }
        if (!flag) {//没有数据交换则表示排序完成
            break;
        }
    }
    return array;
}
  • 冒泡排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法

  • 冒泡排序是稳定算法:相邻元素大小相等时不交换元素位置。

  • 冒泡排序的最好最坏时间复杂度:
    1)最好时间复杂度:完全有序情况,需要进行一次冒泡操作,比较n次即可,最好时间复杂度为O(n);
    2)最坏时间复杂度:完全无序情况,进行n次冒泡操作,最坏时间复杂度为O(n2)。

2.2冒泡排序的平均时间复杂度

  • 有序度:数组中具有有序关系的元素对的个数

  • 逆序度:数组中具有与给定关系相反的元素对的个数

  • 满有序度:完全有序的数组中有序度元素对的个数

  • 逆序度 = 满有序度 - 有序度

  • 冒泡操作每做一次,有序度加一

先来分析交换次数。最好情况下,需要0次交换;最坏情况下需要n(n-1)/2次交换,取平均值n(n-1)/4。

移动次数肯定比交换次数多,同时平均时间复杂度不可能比最坏情况复杂度O(n2)大,所以平均时间复杂度为O(n2)。

3.插入排序

3.1 插入排序

将数据分为已排序区和未排序区。每次从未排序区取一个数字在已排序区找到合适的位置插入(插入位置开始往后的元素要依次向后移动一位)。

对于一个给定的序列,元素的移动次数是有限的,就等于逆序度。
可以这么理解:假设给定长度为n的完全有序的序列,要将第n+1个元素插入到此序列中,需要将前n个元素中比第n+1个元素大的元素后移一位。比如给定序列[1,2,3,4,6,7,8],要将5插入到[1,2,3,4,6,7,8]中。实际上就是要对[1,2,3,4,6,7,8,5]进行插入排序,此序列逆序度为3。插入排序时,需要将[6,7,8]向后移动一位,而[6,7,8]和元素5正好是所有的逆序对。与要插入的元素构成逆序对的每一个元素需要向后移动一位,所以移动次数就等于逆序度。

public int[] insertionSort(int[] array) {
    if (array.length <= 0) {
        return null;
    }
    for (int i = 1; i < array.length; i++) {
        int toSort = array[i];
        int j = i - 1;
        // 和已排序区所有元素比较
        // 从后向前比较,如果已排序区元素a大,就将a后移一位
        for (; j >= 0; j--) {
            if (array[j] > toSort) {
                array[j + 1] = array[j];
            }
            else {
                break;// 不需移动则说明已排序区有序
            }
        }
        array[j + 1] = toSort;
    }
    return array;
}
  • 插入排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法

  • 插入排序是稳定算法:我们可以将后出现的元素插入到先出现元素的后面,所以是稳定算法。

  • 插入排序的最好最坏时间复杂度:
    1)最好时间复杂度:完全有序情况,需要进行一次冒泡操作,比较n次即可,最好时间复杂度为O(n)。
    2)最坏时间复杂度:完全无序情况,每次需要将新元素插入到第一个位置,最坏时间复杂度为O(n2)。

  • 插入排序的平均时间复杂度:在数组某个位置插入一个数据,需要将该位置后面的数据全部向后移动一位。所以,插入位置从头开始的话,所需时间为n,n-1,n-2,n-3,……3,2,1。概率都为1/n,故平均时间复杂度为(1+2+3+……+n)/n=O(n)。插入排序需要进行n次插入操作,所以平均时间复杂度为O(n2)。

4. 选择排序

4.1 选择排序

将数据分为已排序区和未排序区。每次在未排序区选择一个最小的元素,放到已排序区末尾。

public int[] chooseSort(int[] array) {
    if (array.length <= 0) {
        return null;
    }
    for (int i = 0; i < array.length; i++) {
        int min = array[i];// 初始化假设最小值为未排序区第一个元素
        int minIndex = i;// 初始化最小值的下标为未排序区第一个元素的下标
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < min) {
                min = array[j];
                minIndex = j;// 找到最小值下标
            }
        }
        // 如果未排序区第一个数是最小值,则此轮不需要移动元素
        if (minIndex == i) {
            continue;
        }
        // 将未排序区中最小值前面的数字从后往前遍历,向后移
        for (int k = minIndex; k > i; k--) {
            array[k] = array[k - 1];
        }
        array[i] = min;
    }
    return array;
}
  • 选择排序是原地排序算法:使用常量级别的空间,空间复杂度为O(1),是原地排序算法。

  • 选择排序是不是稳定性算法:当第一个元素a不是最小值时,假设最小值为元素b,而a和b之间还有元素c=a,即a、b、c在原数组中顺序为a、c、b(其中a=c,a在c前面)。第一次排序时a和b交换位置,变成b、c、a(a在c后面)。

  • 最好时间复杂度、最坏时间复杂度和平均时间复杂度都为O(n2)。

5. 冒泡排序和插入排序效率比较

冒泡排序和插入排序的时间复杂度都是O(n2)。
冒泡排序中每次进行数据交换需要3个赋值操作,插入排序每次移动只需要1个赋值操作,所以插入排序效率更高。


以上是关于最熟悉的几种排序——冒泡排序插入排序和选择排序的主要内容,如果未能解决你的问题,请参考以下文章

Java中的几种排序算法:冒泡排序,插入排序,二分法排序,简单排序,快速排序

java 交换排序之(冒泡排序快速排序)

学习Java绝对要懂的,Java编程中最常用的几种排序算法!

几种排序方式的java实现(01:插入排序,冒泡排序,选择排序,快速排序)

Java中的几种排序方法

几种排序算法及Java实现排序的几种方式