ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

程序员基本功系列2——排序算法

2022-01-29 10:33:34  阅读:164  来源: 互联网

标签:sort arr int 复杂度 程序员 数组 基本功 排序


1、衡量排序算法的标准

  其实几乎所有算法都可以从几个方便进行衡量:执行效率、内存开销、稳定性。

  排序算法也一样,主要从:

    • 时间复杂度,包括:最好情况、最坏情况、平均时间复杂度、还有比较和交换的次数

    • 空间复杂度,如:原地排序

    • 排序算法的稳定性,即相同数值的元素,排序后的前后顺序不变则称为稳定排序

  来汇总一下常用排序算法:

算法分类 时间复杂度 空间复杂度 是否基于比较 是否稳定排序
冒泡、插入、选择 O(n2) O(1) 冒泡和插入是稳定排序,选择是非稳定排序
快排、归并 O(nlogn)    
桶排序、计数、基数 O(n)    

2、冒泡、插入和选择

2.1、冒泡排序

  举个简单的例子:对数组[4,5,6,3,2,1]进行排序。看下冒泡过程分解:

      

  冒泡排序包含两个关键操作:比较和交换,根据上面的分析,我们写出具体的代码:     

// 冒泡排序,a表示数组,n表示数组大小
public void bubbleSort(int[] a, int n) {
  if (n <= 1) return;
 
 for (int i = 0; i < n; ++i) {
    // 提前退出冒泡循环的标志位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j) {
      if (a[j] > a[j+1]) { // 交换
        int tmp = a[j];
        a[j] = a[j+1];
        a[j+1] = tmp;
        flag = true;  // 表示有数据交换      
      }
    }
    if (!flag) break;  // 没有数据交换,提前退出
  }
}

  冒泡排序比较简单,注意这里我们设置了一个退出标志位,当数组已经当前位置后续元素已经有序的情况下,减少不必要的循环逻辑。

  冒泡排序的最好情况下时间复杂度,即数组有序时(1,2,3,4,5,6),是O(n);最坏情况时间复杂度,即数组逆序(6,5,4,3,2,1),是O(n2);平均时间复杂度是 O(n2)。

  冒泡排序是原地排序,空间复杂度 O(1)。

  冒泡排序是稳定是排序,因为 a[j] == a[j+1] 时没有进行交换,所以前后顺序不会变。

2.2、插入排序

  插入排序思想:将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。然后取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。

  对数组[4,5,6,3,2,1]进行排序,看下插入过程分解:

      

  根据以上分析,我们写出具体代码:

// 插入排序,a表示数组,n表示数组大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;

  for (int i = 1; i < n; ++i) {
    int value = a[i];// 查找插入的位置
    for (int j=i-1; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 数据移动
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入数据,注意这里是a[j+1],因为循环结束找到要插入位置后,后面的--j还会执行。
  }
}

  插入排序的最好情况下时间复杂度,即数组有序时(1,2,3,4,5,6),是O(n);最坏情况时间复杂度,即数组逆序(6,5,4,3,2,1),是O(n2);平均时间复杂度是 O(n2)。

  插入排序是原地排序,空间复杂度 O(1)。

  插入排序是稳定是排序,因为 a[j] == value 时没有将 a[j] 移动,所以前后顺序不会变。

2.3、选择排序

  选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

      

  根据以上分析,我们写出代码:

// 选择排序,a表示数组,n表示数组大小
public void selectSort(int[] a, int n) {
  if (n <= 1) return;

  for (int i = 0; i < n; ++i) {
    int min_idx = i;
    // 找到最小元素的位置
    for (int j=i+1; j < n; ++j) {
      if (a[j] < a[min_idx]) {
        min_idx = j;
      } 
    }
    //移动未排序区最小元素到排序区
    if(min_idx != i){
       int tmp = a[i];
       a[i] = a[min_idx];
       a[min_idx] = tmp;
    }
  }
}

  选择排序的最好情况下时间复杂度,即数组有序时(1,2,3,4,5,6),是O(n);最坏情况时间复杂度,即数组逆序(6,5,4,3,2,1),是O(n2);平均时间复杂度是 O(n2)。

  选择排序是原地排序,空间复杂度 O(1)。

  选择排序是不稳定是排序,因为每次都要找到未排序区最小元素和前面的元素交换位置,这样破坏了稳定性。例如:[5,8,5,2,4],当第一个5和2交换位置时稳定性就被破坏了。

3、快速和归并

  快速排序和归并排序都用到了分治思想,适合大规模数据排序,比上面三种更常用。

3.1、归并排序

   归并排序的核心思想是:把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

      

  分治思想都会用到递归,我们来看下递推公式:

    merge_sort(p..r) = merge_sort(p...m) + merge_sort(m+1...r),终止条件:当 p >= r 时不再继续分解。

  到这里,分解的过程就结束了,但是分解后还有一个合并的过程,就是将已经有序的 A[p...m] 和 A[m+1...r] 合并成一个有序数组,然后放入 A[p...r]。

  合并的逻辑:申请一个临时数组 tmp,大小与 A[p...r] 相同。用两个游标 i 和 j,分别指向 A[p...m]和 A[m+1...r]的第一个元素。比较这两个元素 A[i] 和 A[j],如果 A[i]<=A[j],就把 A[i] 放入到临时数组 tmp,并且 i 后移一位,否则将 A[j]放入到数组 tmp,j 后移一位。继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,临时数组中存储的就是两个子数组合并之后的结果了。最后再把临时数组 tmp 中的数据拷贝到原数组 A[p...r]中。

      

 

  根据以上分析,我们写出整个分解与合并的代码:

public class MergeSort {

    public static void main(String[] args) {
        int[] arr = {4,5,6,3,2,1};
        int len = arr.length;
        sort(arr,0,len-1);
        for (int item : arr){
            System.out.print(item);
        }
    }

    /**
     * 分解
     */
    private static void sort(int[] arr,int left,int right){
        //终止条件
        if (left >= right)return;

        int mid = (left+right) / 2;

        sort(arr,left,mid);
        sort(arr,mid+1,right);
        //如果两个数组已经有序,就不用再合并了
        if (arr[mid] <= arr[mid+1]){
            return;
        }
        //合并两段区间
        merge(arr,left,mid,right);
    }

    /**
     * 合并
     */
    private static void merge(int[] arr,int left,int mid,int right){
        //创建临时数组
        int[] tmp = new int[right-left+1];

        int i=left,j=mid+1;
        for (int k=0;k<tmp.length;k++){
            //如果左侧
            if (i == mid+1){
                tmp[k] = arr[j++];
            }else if (j == right+1){
                tmp[k] = arr[i++];
            }
            //这一步如果去掉等号,就破坏调了稳定性
            else if (arr[i] <= arr[j]){
                tmp[k] = arr[i++];
            }else {
                tmp[k] = arr[j++];
            }
        }
        //采用Java自带的数组拷贝,也可以写个循环
        System.arraycopy(tmp, 0,arr, left, tmp.length);
    }
}
public class MergeSort {

    public static void main(String[] args) {
        int[] arr = {4,5,6,3,2,1};
        int len = arr.length;
        sort(arr,0,len-1);
        for (int item : arr){
            System.out.print(item);
        }
    }

    /**
     * 分解
     */
    private static void sort(int[] arr,int left,int right){
        //终止条件
        if (left >= right)return;

        int mid = (left+right) / 2;

        sort(arr,left,mid);
        sort(arr,mid+1,right);
        //如果两个数组已经有序,就不用再合并了
        if (arr[mid] <= arr[mid+1]){
            return;
        }
        //合并两段区间
        merge(arr,left,mid,right);
    }

    /**
     * 合并
     */
    private static void merge(int[] arr,int left,int mid,int right){
        //创建临时数组
        int[] tmp = new int[right-left+1];

        int i=left,j=mid+1;
        for (int k=0;k<tmp.length;k++){
            //如果左侧
            if (i == mid+1){
                tmp[k] = arr[j++];
            }else if (j == right+1){
                tmp[k] = arr[i++];
            }
            //这一步如果去掉等号,就破坏调了稳定性
            else if (arr[i] <= arr[j]){
                tmp[k] = arr[i++];
            }else {
                tmp[k] = arr[j++];
            }
        }
        //采用Java自带的数组拷贝,也可以写个循环
        System.arraycopy(tmp, 0,arr, left, tmp.length);
    }
}

 

 

 

 

 

  

标签:sort,arr,int,复杂度,程序员,数组,基本功,排序
来源: https://www.cnblogs.com/jing-yi/p/15853933.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有