zoukankan      html  css  js  c++  java
  • 经典排序算法原理解析与优劣对比

    衡量一个排序算法的优劣,我们主要会从以下 3 个角度进行分析:

    1时间复杂度,具体包括,最好时间复杂度、最坏时间复杂度以及平均时间复杂度。

    2空间复杂度,如果空间复杂度为 1,也叫作原地排序。

    3稳定性,排序的稳定性是指相等的数据对象,在排序之后,顺序是否能保证不变

       

    【冒泡排序】

    冒泡排序最好时间复杂度是 O(n)

    冒泡排序最坏时间复杂度会比较惨,是 O(n*n)

    平均时间复杂度也是 O(n*n)

    冒泡排序不需要额外的空间,所以空间复杂度是 O(1)

    冒泡排序过程中,当元素相同时不做交换,所以冒泡排序是稳定的排序算法

    实现如下:

    public static void bubbleSort(int[] nums) {

    int size = nums.length;

    for (int i=0; i<size; i++) {

    for (int j=i; j<size; j++) {

    if (nums[i] > nums[j]) {

    int temp = nums[i];

    nums[i] = nums[j];

    nums[j] = temp;

    }

    }

    }

    }

       

    【插入排序】

    插入排序最好时间复杂度是 O(n)

    插入排序最坏时间复杂度则需要 O(n*n)

    插入排序的平均时间复杂度是 O(n*n)

    插入排序不需要开辟额外的空间,所以空间复杂度是 O(1)

    插入排序是稳定的排序算法;

    实现如下:

    public static void sortInsert(int[] nums) {

    int size = nums.length;

    for (int i = 1; i<size; i++) {

    int temp = nums[i];

    int j=i-1;

    for (; j>=0; j--) {

    if (temp < nums[j]) {

    nums[j+1] = nums[j];

    } else {

    break;

    }

    }

    nums[j+1] = temp;

    }

    }

       

    相同点

       

    插入排序和冒泡排序的平均时间复杂度都是 O(n*n),且都是稳定的排序算法,都属于原地排序

       

    差异点

       

    冒泡排序每轮的交换操作是动态的,所以需要三个赋值操作才能完成;

    而插入排序每轮的交换动作会固定待插入的数据,因此只需要一步赋值操作。

       

    【归并排序】

    归并排序的原理其实就是我们上一课时讲的分治法。

    它首先将数组不断地二分,直到最后每个部分只包含 1 个数据。然后再对每个部分分别进行排序,最后将排序好的相邻的两部分合并在一起,这样整个数组就有序了。

    public static void mergeSort(int[] arr) {

    int len = arr.length;

    int[] result = new int[len];

    merge(arr, result, 0, len - 1);

    }

       

    public static void merge(int[] arr, int[] result, int start, int end) {

    if (start >= end) {

    return;

    }

    int len = end - start;

    int mid = (len >> 1) + start;

    int start1 = start, end1 = mid;

    int start2 = mid + 1, end2 = end;

    merge(arr, result, start1, end1);

    merge(arr, result, start2, end2);

    int k = start;

    while (start1 <= end1 && start2 <= end2) {

    result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];

    }

    while (start1 <= end1){

    result[k++] = arr[start1++];

    }

    while (start2 <= end2){

    result[k++] = arr[start2++];

    }

    for (int i = start; i <= end; i++) {

    arr[i] = result[i];

    }

    }

       

    对于归并排序,它采用了二分的迭代方式,复杂度是 logn

    每次的迭代,需要对两个有序数组进行合并,这样的动作在 O(n) 的时间复杂度下就可以完成。

    因此,归并排序的复杂度就是二者的乘积 O(nlogn)

    同时,它的执行频次与输入序列无关,因此,归并排序最好、最坏、平均时间复杂度都是 O(nlogn)

       

    空间复杂度方面,由于每次合并的操作都需要开辟基于数组的临时内存空间,所以空间复杂度为 O(n)

    归并排序合并的时候,相同元素的前后顺序不变,所以归并是稳定的排序算法

       

    【快速排序】

    快速排序法的原理也是分治法。

    它的每轮迭代,会选取数组中任意一个数据作为分区点,将小于它的元素放在它的左侧,大于它的放在它的右侧。

    再利用分治思想,继续分别对左右两侧进行同样的操作,直至每个区间缩小为 1,则完成排序。

    public static void fastSort(int[] nums, int start, int end) {

    int low = start;

    int high = end;

    if (low >= high) {

    return;

    }

       

    int standData = nums[start];

    while (low < high) {

    while (low < high && nums[high] >= standData) {

    high--;

    }

    while (low < high && nums[low] <= standData) {

    low++;

    }

    int temp = nums[low];

    nums[low] = nums[high];

    nums[high] = temp;

    }

    nums[start] = nums[low];

    nums[low] = standData;

       

    if(low - 1 > start) {

    fastSort(nums, start, low - 1);

    }

    if(high + 1 < end) {

    fastSort(nums, high + 1, end);

    }

    }

    在快排的最好时间的复杂度下,如果每次选取分区点时,都能选中中位数,把数组等分成两个,那么此时的时间复杂度和归并一样,都是 O(n*logn)

       

    而在最坏的时间复杂度下,也就是如果每次分区都选中了最小值或最大值,得到不均等的两组。那么就需要 n 次的分区操作,每次分区平均扫描 n / 2 个元素,此时时间复杂度就退化为 O(n*n)

       

    快速排序法在大部分情况下,统计上是很难选到极端情况的。因此它平均的时间复杂度是 O(n*logn)

       

    快速排序法的空间方面,使用了交换法,因此空间复杂度为 O(1)

       

    很显然,快速排序的分区过程涉及交换操作,所以快排是不稳定的排序算法

       

    【总结】

    如果对数据规模比较小的数据进行排序,可以选择时间复杂度为 O(n*n) 的排序算法。

    但对数据规模比较大的数据进行排序,就需要选择时间复杂度为 O(nlogn) 的排序算法了。

    归并排序的空间复杂度为 O(n),也就意味着当排序 100M 的数据,就需要 200M 的空间,所以对空间资源消耗会很多

       

    快速排序在平均时间复杂度为 O(nlogn)但是如果分区点选择不好的话,最坏的时间复杂度也有可能逼近 O(n*n)。而且快速排序不具备稳定性,这也需要看你所面对的问题是否有稳定性的需求。

       

       

  • 相关阅读:
    JavaScript正则表达式(四)
    JavaScript三元运算符以及运算符顺序
    JavaScript进制转换
    JavaScript赋值运算符和关系运算符
    JavaScript输出
    hadoop1.2.1的安装
    SSH免费登录
    使用java poi解析表格
    【深入理解JVM】:Java类继承关系中的初始化顺序
    解决yum安装mysql时Requires: libc.so.6(GLIBC_2.17)(64bit)
  • 原文地址:https://www.cnblogs.com/liufarui/p/13213908.html
Copyright © 2011-2022 走看看