排序(Java)


一、插入排序

直接插入排序-原理:
整个区间被分为:

  1. 有序区间
  2. 无序区间

每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入

   
    public static void insertSort(long[] array) {
        // 一共要取多少个元素来进行插入过程(无序区间里有多少个元素)
        for (int i = 0; i < array.length - 1; i++) {
            // 有序区间 [0, i]  至少在 i == 0 的时候得有一个元素
            // 无序区间 [i + 1, n)

            // 先取出无序区间的第一个元素,记为 k
            long k = array[i + 1];

            // 从后往前,遍历有序区间{
            // 找到合适的位置退出
            // 所谓合适的位置,就是第一次 k >= array[j] 的位置
            int j;
            for (j = i; j >= 0 && k < array[j]; j--) {
                array[j + 1] = array[j];        // 将不符合条件的数据往后般一格
            }

            array[j + 1] = k;
        }
    }

时间复杂度:
最好情况是 O(n)
平均/最坏情况是 O(n^2)

空间复杂度: O(1)
稳定性: 具备稳定性

二、希尔排序

希尔排序-原理:
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap>1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。
   
    // 希尔排序
    public static void shellSort(long[] array) {
        int gap = array.length / 2;
        while (gap != 1) {
            insertSortWithGap(array, gap);
            gap = gap / 2;
        }
        // 最后再执行一次插排
        insertSort(array);
    }
    // gap: 间隔 / 一共多少组
    private static void insertSortWithGap(long[] array, int gap) {
        // 外围的循环次数是 n - gap 次
        for (int i = 0; i < array.length - gap; i++) {
            // 一共有 gap 个分组
            // 认为一开始 [0, i + gap) 有序
            // [i + gap, n) 无序
            long k = array[i + gap];

            int j;
            // j 下标只去找同一组的元素比较,所以每次跳过 gap
            for (j = i; j >= 0 && k < array[j]; j = j - gap) {
                array[j + gap] = array[j];
            }

            array[j + gap] = k;
        }
    }

时间复杂度:
最好情况是:O(n)
平均情况是:O(n^1.3)
最坏情况是:O(n^2)

空间复杂度: O(1)
稳定性: 不具备稳定性

三、选择排序

直接选择排序-原理:
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。

升序

   
    public static void selectSort1(long[] array) {
        // 每次选择出最大的数,放到最后去
        // 一共要选择出 n - 1 个数
        for (int i = 0; i < array.length - 1; i++) {
            // 通过遍历无序区间,只需要找到最大的数最在的位置就行(以下标的形式体现)不要做交换
            // 无序 [0, n - i)
            int maxIndex = 0;   // 假设最大的数放在一开始
            for (int j = 1; j < array.length - i; j++) {
                if (array[j] > array[maxIndex]) {
                    // 说明,找到了无序区间的新的最大的数
                    // 所以,记录最大数的下标
                    maxIndex = j;
                }
            }

            // 遍历完成之后,无序区间的最大的数就放在 maxIndex 所在下标处
            // array[maxIndex] 是最大数

            // 交换 [maxIndex] 和 无序区间的最后一个元素 [n - i - 1]
            swap(array, maxIndex, array.length - i - 1);
        }
    }

降序

public static void selectSort2(long[] array) {
        // 每次选择出最小的数,放到最后去
        // 一共要选择出 n - 1 个数
        for (int i = 0; i < array.length - 1; i++) {
            int minIndex = 0;
            for (int j = 1; j < array.length - i; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            swap(array, minIndex, array.length - 1 - i);
        }
    }

时间复杂度::O(n^2)

空间复杂度: O(1)
稳定性: 不具备稳定性

四、堆排序

堆排序-原理:
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意: 排升序要建大堆;排降序要建小堆。

   
    public static void heapSort(long[] array) {
        // 1. 建立大堆
        createBigHeap(array);

        // 2. 遍历 n - 1 次
        for (int i = 0; i < array.length - 1; i++) {
            // 2.1 交换之前的无序区间 [0, n - i)
            swap(array, 0, array.length - i - 1);
            // 交换之后的无序区间 [0, n - i - 1),元素个数 n - i - 1 个
            // 2.1 对堆的 [0] 进行向下调整,堆里的元素个数就是无序区间的元素个数
            shiftDown(array, array.length - i - 1, 0);
        }
    }

    private static void createBigHeap(long[] array) {
        for (int i = (array.length - 2) / 2; i >= 0; i--) {
            shiftDown(array, array.length, i);
        }
    }

    private static void shiftDown(long[] array, int size, int index) {
        while (2 * index + 1 < size) {
            int maxIndex = 2 * index + 1;
            int right = maxIndex + 1;
            if (right < size && array[right] > array[maxIndex]) {
                maxIndex = right;
            }
            if (array[index] >= array[maxIndex]) {
                return;
            }
            swap(array, index, maxIndex);
            index = maxIndex;
        }
    }


    private static void swap(long[] array, int i, int j) {
        long t = array[i];
        array[i] = array[j];
        array[j] = t;
    }

时间复杂度: O(n * log(n))

空间复杂度: O(1)
稳定性: 不具备稳定性

五、冒泡排序

冒泡排序-原理:
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序

   
    public static void bubbleSort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean sorted = true;
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {  
                    sorted = false;
                    swap(array, j, j + 1);
                }
            }
            if (sorted) {
                return;
            }
        }
    }

时间复杂度:
最好情况是 O(n)
平均/最坏情况是 O(n^2)

空间复杂度: O(1)
稳定性: 具备稳定性

六、快速排序

快速排序-原理:

  1. 从待排序区间选择一个数,作为基准值(pivot);
  2. Partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度==0,代表没有数据。
   
       public static void quickSort(long[] array) {
        quickSortRange(array, 0, array.length - 1);
    }

    // 为了代码书写方便,我们选择使用左闭右闭的区间表示形式
    // 让我们对 array 中的从 from 到 to 的位置进行排序,其他地方不用管
    // 其中,from,to 下标的元素都算在区间的元素中
    // 左闭右闭的情况下,区间内的元素个数 = to - from + 1;
    private static void quickSortRange(long[] array, int from, int to) {
        if (to - from < 1) {
            // 区间中元素个数 <= 1 个
            return;
        }

        // 挑选中区间最右边的元素 array[to]
        // array[to] 还是 array[to - 1] 还是 array[array.length] 还是 array[array.length - 1] 呢?
        int pi = partitionMethodA(array, from, to);
        // 小于等于 pivot 的元素所在的区间如何表示 array, from, pi - 1
        // 大于等于 pivot 的元素所在的区间如何表示 array, pi + 1, to

        // 按照分治算法的思路,使用相同的方式,处理相同性质的问题,只是问题的规模在变小
        quickSortRange(array, from, pi - 1);    // 针对小于等于 pivot 的区间做处理
        quickSortRange(array, pi + 1, to);   // 针对大于等于 pivot 的区间做处理
    }

    /**
     * 以区间最右边的元素 array[to] 最为 pivot,遍历整个区间,从 from 到 to,移动必要的元素
     * 进行分区
     * @param array
     * @param from
     * @param to
     * @return 最终 pivot 所在的下标
     */
    private static int partitionMethodA(long[] array, int from, int to) {
        // 1. 先把 pivot 找出来
        long pivot = array[to];
        // 2. 通过定义 left 和 right 两个下标,将区间划分出来
        int left = from;
        int right = to;
        // [from, left)   都是 <= pivot 的
        // [left, right)  都是未参与比较的
        // [right, to]    都是 >= pivot 的

        // 循环,保证每个元素都参与了和 pivot 的比较
        // 也就是,只要 [left, right) 区间内还有元素,循环就应该继续
        while (left < right) {
//        while (right - left > 0) {
            // 先让左边进行比较

            // 随着 left 在循环过程中一直在 left++,请问 left < right 的条件能一定保证么
            // 不一定,所以,我们时刻进行 left < right 条件的保证
            // 并且,只有在 left < right 成立的情况下,array[left] 和 pivot 的比较才有意义
            // left < right && array[left] <= pivot 的顺序不能交换
            while (left < right && array[left] <= pivot) {
                left++;
            }
            // 循环停止时,说明 array[left] > pivot

            while (left < right && array[right] >= pivot) {
                right--;
            }
            // 循环停止时,说明 array[right] < pivot

            // 两边都卡住时,交换 [left] 和 [right] 位置的元素
            long t = array[left];
            array[left] = array[right];
            array[right] = t;
        }

        // 说明 left == right,说明 [left, right) 区间内一个元素都没有了
        // 所有元素都和 pivot 进行过比较了,然后都在各自应该的位置上了
        // 并且 array[left] 一定是 >= pivot 的第一个元素(不给大家证明了)
        long t = array[to];
        array[to] = array[left];
        array[left] = t;

        // 返回 pivot 最终所在下标
        return left;
    }


    private static int partitionMethodB(long[] array, int from, int to) {

        long pivot = array[to];
        int left = from;
        int right = to;

        while (left < right) {

            while (left < right && array[left] <= pivot) {
                left++;
            }

            array[right] = array[left];

            while (left < right && array[right] >= pivot) {
                right--;
            }

            array[left] = array[right];

        }

        array[left] = pivot;
        return left;
    }

时间复杂度:
最好/平均情况是 O(n * log(n))
最坏情况是 O(n^2)

空间复杂度:
最好/平均情况是 O(log(n))
最坏情况是 O(n)

稳定性: 不具备稳定性

七、归并排序

归并排序-原理:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

   
   public static void mergeSort(long[] array) {
        mergeSortRange(array, 0, array.length);
    }

    // 这里的 [from, to) 是左闭右开来表示区间的
    private static void mergeSortRange(long[] array, int from, int to) {
        int size = to - from;
        // 情况1:如果区间内元素个数 <= 1 个,什么都不需要做
        if (size <= 1) {
            return;
        }

        // 其他情况
        // 1. 找到区间的中间位置的下标
        int mid = from + (size / 2);

        // 2. 优先对左 [from, mid) 和右 [mid, to) 两个小区间先进行排序(使用同样的方式处理:分治思想)
        mergeSortRange(array, from, mid);
        mergeSortRange(array, mid, to);

        // 3. 有了两个分别各自有序的小区间 [from, mid) 和 [mid, to)
        //    通过一定的方式,将 [from, mid) [mid, to) 合并到 [from, to) 都是有序的
        //    两个有序数组区间合并到一个有序数组区间(需要结果放回原地去)
        merge(array, from, mid, to);
    }

    private static void merge(long[] array, int from, int mid, int to) {
        // 先计算出来额外空间需要多个,计算两个区间加起来多少个元素
        int size = to - from;
        // 申请一个额外的数组作为临时保存的地方
        long[] other = new long[size];

        int left = from;    // 左边小区间的下标
        int right = mid;    // 右边小区间的下标
        int dest = 0;       // 临时空间的下标

        // 只要左右两个小区间还有元素要参与比较
        while (left < mid && right < to) {
            if (array[left] <= array[right]) {
                other[dest++] = array[left++];
            } else {
                other[dest++] = array[right++];
            }
        }

        // 其中一个区间的元素取完了,另一个区间里一定还有元素,再把剩余的元素,统统放入 other
        // 看起来左右两个都写了,但执行起来的时候一定只有一个会执行
        while (left < mid) {
            other[dest++] = array[left++];
        }
        while (right < to) {
            other[dest++] = array[right++];
        }

        // 把 other 中的有序元素,复制回 array 中,要注意下标的问题
        for (int i = 0; i < size; i++) {
            array[from + i] = other[i];     // array 下标的基准是从 [from] 开始,other 下标的基准是从 [0] 开始
            // offset(偏移)是一致的,都是 i
        }
    }

时间复杂度: O(n * log(n))

空间复杂度: O(n)
稳定性: 具备稳定性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值