zoukankan      html  css  js  c++  java
  • 三、排序之冒泡、插入、选择

    一、衡量一个排序算法

    1.1、排序算法的执行效率

    1. 最好情况、最坏情况、平均情况时间复杂度
    2. 时间复杂度的系数、常数 、低阶
      时间复杂度反应的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。
      但是实际的软件开发中,我们排序的可能是10个、 100个、 1000个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数、常数、低阶也考虑进来。
    3. 比较次数和交换(或移动)次数
      冒泡、插入、选择都是基于比较的排序算法。基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。
      所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。

    1.2、排序算法的内存消耗

    • 算法的内存消耗可以通过空间复杂度来衡量,排序算法也不例外。
    • 不过,针对排序算法的空间复杂度,还有一个新的概念, 原地排序(Sorted in place)。
      原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。我们今天讲的三种排序算法,冒泡、插入、选择原地排序算法。

    1.3、排序算法的稳定性

    • 如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
    • 比如一组数据 2, 9, 3, 4, 8, 3,按照大小排序之后就是 2, 3, 3, 4, 8, 9。
    • 这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那我们就把这种排序算法叫作稳定的排序算法。
    • 如果前后顺序发生变化,那对应的排序算法就叫作不稳定的排序算法。

    二、冒泡排序

    • 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较。
    • 看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置。
    • 重复 n 次,就完成了 n 个数据的排序工作。
    • 冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法
    • 在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候。
    • 我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法
    • 最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 O(n)
    • 而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 O(n²)
    冒泡排序

    可以看出,经过一次冒泡操作之后, 6这个元素已经存储在正确的位置上。要想完成所有数据的排序,我们只要进行6次这样的冒泡操作就行了。
    冒泡排序

    public static void bubbleSort(int[] arr) {
        if (arr.length <= 1)
            return;
        for (int i = 0; i < arr.length; i++) {
            boolean b = false;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                    b = true;
                }
            }
            // 一次循环结束,如果没有元素发生交换,则证明整体有序,直接跳出循环
            if (!b) {
                break;
            }
        }
    }
    

    三、插入排序(Insertion Sort)

    插入排序
    • 将数组中的数据分为两个区间, 已排序区间和未排序区间。
    • 初始已排序区间只有一个元素,就是数组的第一个元素。
    • 插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。
    • 重复这个过程,直到未排序区间中元素为空,算法结束。
    插入排序
    • 插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法
    • 在插入排序中,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,
    • 所以插入排序是稳定的排序算法
    • 如果要排序的数据已经是有序的,我们并不需要搬移任何数据。
    • 如果我们从尾到头在有序数据组里面查找插入位置,每次只需要比较一个数据就能确定插入的位置。
    • 所以这种情况下,最好是时间复杂度为 O(n)。注意,这里是从尾到头遍历已经有序的数据。
    • 如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,所以需要移动大量的数据,所以最坏情况时间复杂度为O(n²)
    • 在数组中插入一个数据的平均时间复杂度是 O(n)。所以,对于插入排序来说,每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为 O(n²)
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        if (n <= 1) {
            return;
        }
        for (int i = 0; i < n; i++) {
            int value = arr[i];
            int j = i - 1;
            // 查找插入的位置
            for (; j >= 0; --j) {
                if (arr[j] > value) {
                    arr[j + 1] = arr[j];//移动数据
                } else {
                    break;
                }
            }
            arr[j + 1] = value;//插入数据
        }
    }
    

    四、选择排序(Selection Sort)

    • 选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。
    • 选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
    选择排序
    • 选择排序空间复杂度为 O(1),是一种原地排序算法
    • 选择排序一次循环只能找到未排序区间中的最小值,不能判断未排序区间的数据是否整体有序。
    • 所以选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n²)
    • 选择排序是一种不稳定的排序算法,选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性
    public static void selectionSort(int[] arr) {
        int length = arr.length;
        if (length == 1) {
            return;
        }
        int x;
        for (int i = 0; i < length; i++) {
            x = i;
            for (int j = i + 1; j < length; j++) {
                if (arr[x] > arr[j]) {
                   // 这里不做交换,至记录下对应的下标,一次循环结束在交换,减少交换的次数
                   x = j;
                }
            }
            if(x != i){
                int tmp = arr[i];
                arr[i] = arr[x];
                arr[x] = tmp;
            }
        }
    }
    

    总结一下

    空间复杂度 是否稳定 最好时间复杂度 最坏时间复杂度 平均时间复杂度
    冒泡排序 Q(1) Q(n) Q(n²) Q(n²)
    插入排序 Q(1) Q(n) Q(n²) Q(n²)
    选择排序 Q(1) Q(n²) Q(n²) Q(n²)
  • 相关阅读:
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第3节 maven标准目录结构和常用命令_07maven常用命令
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第3节 maven标准目录结构和常用命令_06maven标准目录结构
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第2节 maven的安装和仓库种类_05仓库的种类和彼此关系
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第2节 maven的安装和仓库种类_04maven的安装
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第1节 基本概念_03maven一键构建概念
    阶段2 JavaWeb+黑马旅游网_15-Maven基础_第1节 基本概念_02maven依赖管理的概念
    kmalloc、vmalloc、__get_free_pages()的区别
    如何增加内核模块
    VFS四大对象之四-struct file
    VFS四大对象之三 struct dentry
  • 原文地址:https://www.cnblogs.com/xiexiandong/p/13040515.html
Copyright © 2011-2022 走看看