排序算法 - 二分插入排序、希尔排序(插入排序的优化)

目录

1、二分插入排序

2、希尔排序


   

    上一篇博客分析了时间复杂度为O(N²)的三种排序方式,并且基于稳定特性的插入排序和冒泡排序做了性能对比,当数据量级比较小的使用经常会选择插入排序。然而插入排序本身还可以进行优化,让其性能进一步提升,二分插入排序和希尔排序都是在普通插入排序的基础上进一步优化。

1、二分插入排序

    插入排序本身就是往后遍历,每次轮训都会把前N个元素排好序,然后再将N+1个元素插入已经排好序的“子数组”里面。直接插入排序判断往哪个下标位置插入时,是遍历了整个子数组,在数据结构 - 数组中分析过,查询的最好时间复杂度是O(1),最坏时间复杂度是O(N),平均时间复杂度是O(N)。这里我们忽略了一个重要的特性,或者说是丢弃了前面子数组有序的特性。此时我们可以将此部分的查找过程,从时间复杂度O(N) 优化成二分查询的O(logN)。

public class BinaryInsertSort {
    public static void main(String[] args) {
        int[] array = new int[]{12, 34, -34, 454, 657, 33, 89, 67, 68, 99, -23, 34};
        System.out.println("排序前:" + Arrays.toString(array));
        binaryInsertSort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }

    /**
     * 二分插入排序
     * @param array 待排序的数组
     */
    public static void binaryInsertSort(int[] array) {
        if (array.length < 2) {
            return;
        }
        // 记录当轮的值
        int value = 0;
        int low = 0, high = 0, middle = 0;
        for (int i = 1; i < array.length; i++) {
            low = 0;
            value = array[i];
            high = i - 1;
            while (low <= high) {
                middle = (high + low) >> 1;
                if (value < array[middle]) {
                    // 插入点为低半区,否则插入点在高半区
                    high = middle - 1;
                } else {
                    low = middle + 1;
                }
            }

            // 已经用临时变量value记录了需要挪动的位置值,此时从后往前将插入点后面所有元素往后挪动一位
            for (int j = i - 1; j >= low; j--) {
                array[j+ 1] = array[j];
            }
            //都往后挪动完了,再插入准确位置
            array[low] = value;
        }
    }
}

 

2、希尔排序

    希尔排序针对逆序度比较高的情况,一般的插入排序需要将最小元素移动的下标为0的位置,单个的时间复杂度近似 O(N) 所以希尔排序能快速的将逆序度非常高调整为近似顺序。

比如: [8, 9, 1, 7, 2, 3, 5, 4, 6, 0] 这样的数组需要从小到大排列, 一般的插入排序,时间复杂度就是 O(N)

    第一次外层循环:

        初始增量 gap = length / 2 = 5 , 那么整个数组将被分到5个组中, (8,3) (9,5) (1,4) (7,6) (2,0) 再进行比较交换

        当前状态为: [3, 1, 0, 9, 7, 5, 6, 8, 4, 2]

    第二次外层循环:

        缩小增量 gap = 5 / 2 = 2, 那么 将分成两个组 (3, 1, 0, 9, 7) (5, 6, 8, 4, 2) 再进行比较交换

        当前的状态为: [3, 5, 1, 6, 0, 8, 4, 9, 2, 7]

    第三次外层循环:

        继续缩小增量: gap = 2 / 2 = 1, 那么整个被分成一个组 (3, 5, 1, 6, 0, 8, 4, 9, 2, 7)

public class ShellSort {

    public static void main(String[] args) {
        int[] array = new int[]{8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        System.out.println("排序前:" + Arrays.toString(array));
//        shellChangeSort(array);
        shellSort(array);
//        shellMoveSort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }

    /**
     * 希尔排序 针对有序序列在插入时采用交换法
     * @param arr 待排序数组
     */
    public static void shellChangeSort(int[] arr) {
        //增量gap,并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 从第gap个元素,逐个对其所在组进行直接插入排序操作
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                while (j - gap >= 0 && arr[j] < arr[j-gap]) {
                    // 插入排序采用交换法
                    swap(arr, j,j - gap);
                    j -= gap;
                }
            }
        }
    }

    /**
     *  希尔排序 针对有序序列在插入时采用移动法
     * @param arr 待排序数组
     */
    public static void shellMoveSort(int[] arr) {
        // 增量gap,并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 从第gap个元素,逐个对其所在组进行直接插入排序操作
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j - gap]) {
                    while(j - gap >= 0 && temp < arr[j - gap]) {
                        // 移动法
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    arr[j] = temp;
                }
            }
        }
    }

    /**
     *  交换数组元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr, int a, int b) {
        arr[a] = arr[a] + arr[b];
        arr[b] = arr[a] - arr[b];
        arr[a] = arr[a] - arr[b];
    }

    /**
     * 另一种希尔排序写法
     * @param arr 待排序数组
     */
    private static void shellSort(int[] arr) {
        int len = arr.length;
        if (len == 1) {
            return;
        }

        int step = len / 2;
        while (step >= 1) {
            for (int i = step; i < len; i++) {
                int value = arr[i];
                int j = i - step;
                for (; j >= 0; j -= step) {
                    if (value < arr[j]) {
                        arr[j + step] = arr[j];
                    } else {
                        break;
                    }
                }
                arr[j + step] = value;
            }

            step = step / 2;
        }
    }
}

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值