排序

作者: 意大利大炮 | 来源:发表于2021-08-01 17:41 被阅读0次
    相关排序方式
    • 选择、插入、归并、快速等等
    算法 最坏时间 最好时间 最坏交换次数 是否原址
    选择排序 O(n^2) O(n^2) O(n)
    插入排序 O(n^2) O(n) O(n^2)
    归并排序 O(nlogn) O(nlogn) O(nlogn)
    快速排序 O(n^2) O(nlogn) O(n2)
    选择排序
    • 定义
      选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

    • 时间复杂度
      O(n^2): n 表示数组的长度

    • 是否原址
      是(不需要借助其他数组)

    • 程序实现

        public void sort(int A[]) {
            for (int i = 0; i < A.length; i ++) {
                int min = i;
                for (int j = i + 1; j < A.length; j ++) {
                    if (A[j] < min) {
                        min = j;
                    }
                }
                // 最小值放到索引 i 的位置
                int temp = A[min];
                A[i] = A[min];
                A[min] = temp;
            }
        }
    
    插入排序
    • 定义
      插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动
    • 时间复杂度
      O(n^2): n 表示数组的长度

    • 是否原址
      是(不需要借助其他数组)

    • 程序实现

        public void sort(int A[]) {
            for (int i = 1; i < A.length; i ++) {
                for (int j = i; j > 0 && A[j] < A[j - 1]; j --) {
                    // 交换下标j与下标j-1的值
                    int temp = A[j];
                    A[j] = A[j - 1];
                    A[j - 1] = temp;
                }
            }
        }
    
    • 缺点
      在最坏的情况(数组逆序),数组元素移动的次数为O(n^2)
    • 与选择排序的对比
      1. 都是维护两个数组:左边的有序,右边的无序
      2. 插入排序在写入左边的数组时,需要寻找插入点。选择排序则是在现在右边找到最小的,然后直接写入左边数组的末尾。
      3. 插入排序在写入左边数组时,插入点后面的元素都要后移一位
    归并排序
    • 定义
      归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
      归并排序使用分治法,将原问题分解为类似原问题的子问题。
    • 时间复杂度
      O(nlogn): n 表示数组的长度

    • 是否原址
      否(需要借助其他数组)

    • 程序实现

    /**
         * 归并排序
         * @param A 数组
         * @param p 要操作的起始位置
         * @param r 要操作的终点位置
         */
        public void mergeSort(int A[], int p, int r) {
            if (p >= r) {
                return;
            }
            // 分解为两个子问题
            int q = (p + r) / 2;
            mergeSort(A, p, q);
            mergeSort(A, q + 1, r);
            // 归并
            merge(A, p, q, r);
        }
    
        public void merge(int A[], int p, int q, int r) {
            int n1 = q - p + 1; // 数组左部长度
            int n2 = r - q; // 数组左部长度
            // 将数组copy到新数组中
            int B[] = new int[n1 + 1];
            int C[] = new int[n2 + 1];
            System.arraycopy(A, p, B, 0, n1);
            System.arraycopy(A, q + 1, C, 0, n2);
            // 新数组的最后一位设置为最大值, 防止数组越界。这里应该设置为正无穷大,但需要用double。
            // 这里先用int的最大值。所以数组不可以存在大于int最大值的元素
            B[n1] = Integer.MAX_VALUE;
            C[n2] = Integer.MAX_VALUE;
    
            // 归并两个新数组
            int i = 0, j = 0;
            for (int k = p; k <= r; k ++) {
                if (B[i] <= C[j]) {
                    A[k] = B[i];
                    i ++;
                } else {
                    A[k] = C[j];
                    j ++;
                }
            }
        }
    
    • 缺点
      需要额外的内存空间开销
    快速排序
    • 定义
      快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
      (1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
      (2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
      (3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
      (4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

    • 与归并排序的对比
      归并:分解-》排序-》归并......
      快速:粗略排序(数组分为做大右小两部分)-》分解(左右两部分)-》粗略排序......

    • 时间复杂度
      最好:O(nlogn): n 表示数组的长度
      最坏:O(n^2):数组本身就是有序的
      平均:O(nlogn)

    • 是否原址
      是(不需要借助其他数组)

    • 程序实现

     /**
         * 快速排序
         * @param A 数组
         * @param p 要操作的起始位置
         * @param r 要操作的终点位置
         */
        public void quickSort(int A[], int p, int r) {
            if (p >= r) {
                return;
            }
    
            // 将数组分解为左右两部分(两部分都是无序的,但右边的都比左边的大),
            // 并获取中间索引
            int q = partTition(A, p, r);
    
            // 将左右两部分递归排序
            quickSort(A, p, q - 1);
            quickSort(A, q + 1, r);
        }
    
        public int partTition(int A[], int p, int r) {
            int q = p;
    
            // 这里将r设置为主元(在数组
            for (int u = p; u < r; u ++) {
                if (A[u] < A[r]) {
                    int temp = A[q];
                    A[q] = A[u];
                    A[u] = temp;
                    q ++;
                }
            }
            int temp = A[q];
            A[q] = A[r];
            A[r] = temp;
            return q;
        }
    
    • 缺点
      若排序时,主元为最大的,为最差情况,递归次数最多。
      比如,数组本身有序,主元为最后一个元素,那么拆分后的左右两个数组,其中一个为原数组长度-1,每次拆分-1,最多需要拆分2n-1次

    相关文章

      网友评论

          本文标题:排序

          本文链接:https://www.haomeiwen.com/subject/sofrvltx.html