插入排序——直接插入排序与希尔排序

#排序算法–直接插入排序与希尔排序
今天这里介绍插入排序中的直接插入排序及针对其进行的优化排序方法Shell排序。

直接插入排序

直接插入排序是人们能想到的最基本的排序方法之一,这种排序方式将待排序列分为两部分,一部分是已排好序的,另一部分没有排好序。最开始已排序部分中只包含一个元素,因此自然是有序的,每次从另一部分,也就是待排序列中取一个元素与前面已经有序的序列依次比较:将比它大的元素依次右移一个位置,直到找到已排序部分中比它小或最小的那个元素时,即确定了待排元素的正确位置。

动态演示图如下:
直接插入排序示意图
代码实现如下:

public class InsertSort {

    public static void main(String[] args) {
        Integer[] arr = {8, 6, 1, 3, 9, 7, 2, 5, 4};

        // 外层循环:每次将无序部分第一个元素插入到前面有序部分中正确位置
        for (int i = 1; i < arr.length; i++) {
            int j = i - 1;
            int tmp = arr[i];

            // 内层循环:将外层元素依次与已排序列元素比较
            while (j >= 0 && arr[j] > tmp) {
                // 保证已排序部分有序性,较大元素右移
                arr[j + 1] = arr[j];
                j--;
            }
            // 找到排序位置后插入
            arr[j+1] = tmp;
        }
        
		// test-打印
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

}

时间复杂度最优情况下为O(n),即已经有序的序列,只需每个元素比较一次,平均和最差情况为O(n^2)。

Shell排序

希尔排序以直接插入排序为基础,发扬了直接插入排序在小序列、基本有序情况下速度较快的优点,采用分治的思想将原始序列进行分组,再针对分组内的元素分别排序,然后减小分组数量,增多分组内元素个数,直到合并分组为一个。

动态演示图如下:
Shell排序示意图
代码实现如下(演示图与代码不对应):

/**
 * @description: 希尔排序基本实现
 * @author: liyaguang
 * @create: 2018-08-31 13:41
 **/
public class ShellSort {

    public static void main(String[] args) {
        Integer[] arr = {8, 6, 1, 3, 9, 7, 2, 5, 4};

        // 确定分组个数,即确定了每个子序列,也是子序列步长
        for (int i = arr.length / 2; i > 0; i /= 2) {
            // 循环每个子序列,进行排序
            for (int j = 0; j < i; j++) {
                // j、j+i、j+i+i、……、n-i

                // 调用插入排序方法,传入子序列值
                modInsertSort(arr, j, i);
            }
        }

        // 若增量序列中不能保证最后的间距为1,则执行一次扫尾工作,此方法内不需要
        // modInsert(arr, 0, 1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

    }

    /**
     * @param arr      待排数组
     * @param startPos 待排子序列起始位置
     * @param delta    步长/分组个数
     */
    public static void modInsertSort(Integer[] arr, int startPos, int delta) {
        for (int i = startPos + delta; i < arr.length - startPos; i += delta) {

            for (int j = i; j > startPos; j -= delta) {
                if (arr[j] < arr[j - delta]) {
                    // swap(arr, y, y -= i);
                    int tmp = arr[j - delta];
                    arr[j - delta] = arr[j];
                    arr[j] = tmp;
                } else {
                    break;
                }
            }
        }
    }
}

希尔排序时间复杂度跟选择的分组策略息息相关,上面我们实现的“分组数每次除以2递减”的方式实际上时间复杂度仍为O(n ^ 2) ,选择适合的增量序列将有效的改善希尔排序的效率,有的能达到O(n ^ 3/2)、O(n ^ 5/4),有的甚至能达到O(n ^ 7/6),很接近O(nlogn)。

小结

直接插入排序是我们生活中数量不多的排序是最可能想到的一种方法,例如给一个班的成绩排序,我们用一张足够长的空白纸先记录下待排序列中第一个学生的成绩,在拿出第二个学生的成绩与第一个比对,找到合适位置,再拿第三个待排数据与前两个比对,以此实现最终排序的效果。
生活中数据量小、待排序列基本有序的情况下使用直接插入排序很直观、效果也很好,时间复杂度能控制在O(n),而希尔排序则是在直接插入排序的基础上充分发挥了其优点,采用分组、分治的方式进行化大为小,再逐步合并的过程中又充分利用了前面基本有序的结果。

参考资料:
《数据结构与算法》
Insertion sort-Wikiwand
Shellsort-wikiwand

欢迎关注我的公众号,了解更多内容:
我的公众号

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章