算法 | 插入排序、希尔排序

106 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情

插入排序

插入排序也被称为直接插入排序。

插入排序是将一个元素插入到已经排好序的数组或有序表中,从而生成一个新的数组或有序表。

排序原理

假如数组长度为N,使用插入排序对其进行排序,先从最左边开始,先让0 ~ 0范围有序这不废话,再让0 ~ 1范围有序,再让0 ~ 2范围有序,以此类推,直到0 ~ N范围上有序。

给定一组数据:

3, 5, 2, 7, 1, 8, 13, 9, 12

第1次,让0 ~ 0范围有序,即一个数字3肯定是有序的,直接跳过。

第2次,让0 ~ 1范围有序,3, 5也是有序的,直接跳过。

第3次,让0 ~ 2范围有序,2比5小,让2和5进行交换,继续向左,2比3小,让2和3进行交换,排序结果为2, 3, 5, 7, 1, 8, 13, 9, 12

第4次,让0 ~ 3范围有序,2, 3, 5, 7是有序的直接跳过。

第5次,让0 ~ 4范围有序,1比7小,1和7进行交换,1比5小,1和5进行交换,1比3小,1和3进行交换,1比2小,1和2进行交换,排序结果为1, 2, 3, 5, 7, 8, 13, 9, 12

以此类推,直到所有数据都排好序。

代码实现

public class Code05_InsertionSort {

    public static void main(String[] args) {
        int[] arr = {3, 5, 2, 7, 1, 8, 13, 9, 12};
        print(arr);
        insertionSort(arr);
        print(arr);
    }
    
    public static void insertionSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 1; i < arr.length; i++) {
            int j = i;
            while (j - 1 >= 0 && arr[j - 1] > arr[j]) {
                swap(arr, j - 1, j);
                j--;
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }
    
    public static void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

运行输出结果为:

3 5 2 7 1 8 13 9 12 
1 2 3 5 7 8 9 12 13 

优化代码实现

代码嘛,也不是说越长越好,当然短的代码也不一定就是好的。上面这个代码能不能再端一点呢?while这一段代码看起来挺啰嗦的,下面就对while这里做一点小小的优化。

public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 1; i < arr.length; i++) {
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
        }
    }
}

希尔排序

希尔排序也是一种插入排序算法,比简单插入排序更高效,也称为缩小增量排序。

排序原理

将一个无序数组分割几个子数组,每个子数组间隔相同的增量(一般的第一个增量为数组长度的一半),对各个子数组进行插入排序;接着再缩小增量(一般的第二个增量为第一个增量的一半),分割成新的子数组进行插入排序,最后增量缩减为1,对所有元素进行最后一次插入排序。

给定一组数据:

3, 5, 2, 7, 1, 8, 13, 9, 12

数组长度为9,初始增量gap=9/2=4,把整个数组分为4组,[3,1],[5,8],[2,13],[7,9,12]。

对这4组分别进行直接插入排序,排序结果为1, 5, 2, 7, 3, 8, 13, 9, 12,可以看到,小值元素都被调到前面去了,然后缩小增量,gap=4/2=2,数组被分为2组,[1,2,3,13],[5,7,8,9,12]。

对这2组再分别进行直接插入排序,排序结果为1, 5, 2, 7, 3, 8, 12, 9, 13,可以看到离有序更近一步了,再缩小增量gap=2/2=1,整个数组被分为1组。

此时,对数组再做简单的调整即可。

代码实现

public class Code06_ShellSort {
    public static void main(String[] args) {
        int[] arr = {3, 5, 2, 7, 1, 8, 13, 9, 12};
        print(arr);
        shellSort(arr);
        print(arr);
    }

    public static void shellSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int N = arr.length;
        for (int gap = N / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < N; i++) {
                int j = i;
                while (j - gap >= 0 && arr[j] < arr[j - gap]) {
                    swap(arr, j, j - gap);
                    j -= gap;
                }
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

    public static void print(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

运行输出结果为:

3 5 2 7 1 8 13 9 12 
1 2 3 5 7 8 9 12 13