1.插入排序(直接插入排序、二分插入排序和希尔排序)

插入排序(直接插入排序、二分插入排序和希尔排序)

插入排序算法思想:每趟将一个元素,按其关键字值的大小插入到它前面已排序的子序列中,依此重复,直到插入全部元素。

插入排序算法有直接插入排序、二分法插入排序和希尔排序。

1、直接插入排序

直接插入排序(Straight Insertion Sort)算法描述如下:

  • 1、第i (1<i<n)趟,线性序列为{a0,a1,…,ai-1,ai,…,an-1},设前i个元素构成的子序列{a0,a1,…,ai-1}是排序的,将元素a,插入到子序列{a0,a1,…,ai-1}的适当位置,使插入后的子序列仍然是排序的,ai的插入位置由关键字比较大小确定。
  • 2、重复执行第一步,n个元素共需n-1趟,每趟将一个元素ai,插入到它前面的子序列中。关键字序列{32,26,87,72,26,17}的直接插入排序(升序) 过程 如下图所示,以“*” 区别两个关键字相同元素,表示排序子序列。

直接插入排序时间复杂度分析:

  • 最好情况,已排序{1,2,3,4,5,6},O(n)

  • 最坏情况,反序排列{6,5,4,3,2,1},O(n*n)

  • 随机排列,O(n*n)

空间复杂度为:O(1)。

直接插入排序是稳定的。

直接插入排序的算法如下:

package book;

import java.util.Arrays;

/**
 * 直接插入排序(升序)
 */
public class directInsertSort {

    public static void main(String[] args) {
        int[] keys = {32,26,8,26,17,1,90,32};
        //insertSort(keys);
        //insertSort2(keys);
        //insertSort3(keys);
        insertSort4(keys);
        print(keys);
    }

     //直接插入排序4(我觉得这个直接插入排序比另外三个要好)
     //因为在比较时,它从后面一个一个依次比较,前面的序列是已经排好序的,这一点很关键
    public static void insertSort4(int[] keys){
        //从第二个元素开始
        for (int i = 1; i < keys.length; i++) {
            //从下标为i的开始,依次和前面的元素做对比
            //注意此处 j > 0, 不能j >= 0,否则会越界
            for (int j = i; j > 0; j--) {
                if (keys[j - 1] > keys[j]) {
                    //前面的元素比后面的元素大,交换
                    swap(keys, j, j-1);
                }
            }
        }
    }

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

    static void print(int[] arr){
        for (int i : arr) {
            System.out.print(i+",");
        }
    }
    
     //直接插入排序1
    public static void insertSort(int[] keys){
        for (int i = 0; i < keys.length; i++) {
            int temp = keys[i];
            int j;
            //注意temp = keys[i],这里的条件就是 keys[i-1] >keys[i] , 因为升序,所以交换
            for (j = i-1; j >= 0 && keys[j] > temp ; j--) {
                //将大的元素往后移一位
                keys[j+1] = keys[j];
            }
            //注意此时j的值(直到找到比带插入小的数据,或者到达数组头部,把数据插入)
            keys[j+1] = temp;
            System.out.println("第"+i+"趟 temp="+temp+"\t");
        }

        System.out.println("------------");
        for (int key : keys) {
            System.out.println(key);
        }
    }
    
     //直接插入排序2
    public static void insertSort2(int[] keys){
        //从第二个元素开始
        for (int i = 1; i < keys.length; i++) {
            //待排序的元素
            int temp = keys[i];
            int j;
            //遍历已排好序的子序列,范围是从0到i-1
            for (j = i-1; j >= 0; j--) {
                //升序排序,如果前面的元素大于后面的元素,就需要调整元素位置
                if (keys[j] > temp) {
                    //将比较大的元素向后移一位,空出来是待插入的位置。
                    keys[j + 1] = keys[j];
                } else {
                    break;
                }
            }
            //当跳出子序列的循环时,此时j是要比上一次满足条件的j少1,所以此处是j+1
            //直到找到比带插入小的数据,或者到达数组头部,把数据插入
            keys[j + 1] = temp;
        }

        System.out.println("------------------------");
        for (int key : keys) {
            System.out.println(key);
        }
    }

     //直接插入排序3
    public static void insertSort3(int[] keys){
        //从第二个元素开始
        for (int i = 1; i < keys.length; i++) {
            //待排序的元素
            int temp = keys[i];
            int j = i - 1;
            //遍历已排好序的子序列,范围是从0到i-1
            while (j >= 0 && keys[j] > temp) {
                //升序排序,如果前面的元素大于后面的元素,就需要调整元素位置
                //将比较大的元素向后移一位
                keys[j + 1] = keys[j];
                j--;
            }
            //当跳出子序列的循环时,此时j是要比上一次满足条件的j少1
            //直到找到比带插入小的数据,或者到达数组头部,把数据插入
            keys[j + 1] = temp;
        }

        System.out.println("------------------------");
        for (int key : keys) {
            System.out.println(key);
        }
    }
}

2、折半插入排序(二分插入排序)

直接插入排序的每一趟,将一个元素a,插入到它前面的一个排序子序列中,其中采用顺序查找算法寻找a的插入位置。此时,子序列是顺序存储且排序的,这两条正好符合二分法查找要求。

因此,用二分法查找代替直接插入排序中的顺序查找,则构成二分法插入排序。

  • 二分插入排序和直接插入排序的时间复杂度一致,也是O(n^2)

  • 二分插入排序是稳定的

折半插入排序的算法如下:

package book;

import java.util.Arrays;

/**
 * 插入排序(升序)
 *
 * @auther wlw
 * @date 2021/10/25
 */
public class insertSort {

    public static void main(String[] args) {
        int[] keys = {32,26,8,26,17};
        binaryInsertionSort(keys);
    }

    //二分插入排序
    public static void binaryInsertionSort(int[] a) {
        int left, right, temp, j;
        for (int i = 1; i < a.length; i++) {
            temp = a[i];//右手边抓到一张扑克牌
            right = i - 1; //数组的右边界
            left = 0;//初始化
            //采用二分法定位新牌的位置
            while (left <= right){
                int mid = (left + right) / 2;
                if (a[mid] >= temp) {
                    right = mid - 1;
                }
                else {
                    left = mid + 1;
                }
            }
            //找到left就是我要插入的位置,将left及其之后的元素向右移动一位
            for (j = i - 1; j >= left; j--) {
                a[j + 1] = a[j];
            }
            //插入 待排序的元素
            a[left] = temp;
            System.out.println("第"+i+"趟 temp="+temp+"\t");
        }

        System.out.println("------------");
        for (int key : a) {
            System.out.println(key);
        }
    }
}

3、希尔排序(改进的插入排序)

希尔排序,又称缩小增量排序,基本思想就是分组的直接插入排序

由直接插入排序算法分析可知,若数据序列越接近有序,则时间效率越高;再者,当n较小时,时间效率也高。希尔排序正是基于这两点对直接插入排序进行改进的。

希尔排序的算法描述如下:

  • ①将一个数据序列分成若干组,每组由若干相隔一段距离 (称为增量) 的元素组成,在一个组内采用直接插入排序算法进行排序。
  • ②增量初值通常为数据序列长度的一半, 以后每趟增量减半, 最后值为1。随着增量逐渐减小,组数也减少,组内元素个数增加,数据序列接近有序。

关键字序列{38, 55, 65, 97, 27, 76, 27, 13, 19}的希尔排序(升序)过程如下图所示,序列长度为9,增量delta初值为4,序列分为4组进行直接插入排序,之后每趟增量以减半规律变化,经过3趟完成排序。

希尔排序算法实现如下:

package book;

import java.util.Arrays;

/**
 * 插入排序(升序)
 *
 */
public class insertSort {

    public static void main(String[] args) {
        int[] keys2 = {38,55,65,97,27,76,27,13,19};
        //shellSort1(keys2);
        shellSort2(keys2);
        print(keys2);
    }

    //希尔排序1
    public static void shellSort(int[] keys){
        //delta为增量,增量初值通常为数据序列长度的一半,之后每趟增量减半,最后值为1
        for (int delta = keys.length/2; delta > 0; delta = delta/2 ) {
            /**
             * 接下来就是进行在增量确定的情况下,进行组内的直接插入排序
             */
            //先进行分组后的第一组的排序,因第一组第一个元素下标为0,直接插入排序从第二个元素开始
            //并且其他组也是要从第二个元素开始进行直接插入排序,
            //所以此处i = delta,从下标为delta的元素开始,并且i++
            for (int i = delta; i < keys.length; i++) {
                //当前待插入元素
                int temp = keys[i];
                int j;
                for (j = i-delta; j >=0 && keys[j] > temp; j = j-delta) {
                    //较大元素移到改组内的下一个位置上。
                    // 这里j + delta,是因为每组元素相距delta远
                    keys[j + delta] = keys[j];
                }
                //找到位置,将待插入元素插入到此位置上。
                keys[j + delta] = temp;
            }
            System.out.println("delta: "+delta);
        }

        for (int key : keys) {
            System.out.println(key);
        }
    }
    //希尔排序2
    public static void shellSort2(int[] keys){
        //delta为增量,增量初值通常为数据序列长度的一半,之后每趟增量减半,最后值为1
        for (int delta = keys.length/2; delta > 0; delta = delta/2 ) {
            /**
             * 接下来就是进行在增量确定的情况下,进行组内的直接插入排序
             */
            //先进行分组后的第一组的排序,因第一组第一个元素下标为0,直接插入排序从第二个元素开始
            //并且其他组也是要从第二个元素开始进行直接插入排序,
            //所以此处i = delta,从下标为delta的元素开始,并且i++
            for (int i = delta; i < keys.length; i++) {
                //注意j的取值范围,要把每个元素都要比较,又要考虑数据越界
                // 所以j > delta-1 ,要把j - delta = 0这个情况也要想到
                for (int j = i; j > delta-1 ; j = j-delta) {
                   if (keys[j - delta] > keys[j]) {
                       //交换
                       swap(keys, j, j-delta);
                   }
                }
            }
        }
    }

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

    static void print(int[] arr){
        for (int i : arr) {
            System.out.print(i+",");
        }
    }
}

希尔排序算法共有三重循环:

  • 最外层循环for语句以增量delta变化控制进行若干趟扫描,delta初值为序列长度n/2,以后每趟减半,直至1
  • 中间循环for语句进行一趟扫描,序列分为delta组,每组由相距delta远的n/delta个元素组成,每组元素分别进行直接插入排序。
  • 最内层循环for语句进行一组直接插入排序,将一个元素keys[i]插入到其所在组前面的排序子序列中。

希尔排序算法增量的变化规律有多种方案。上述增量减半是一种可行方案。一旦确定增量的变化规律,则一个数据序列的排序趟数就确定了。初始当增量较大时,一个元素与较远的另一个元素进行比较,移动距离较远;当增量逐渐减小时,元素比较和移动距离较近,数据序列则接近有序。最后一次,再与相邻位置元素比较,决定排序的最终位置。下面举个例子(Knuth序列)

//Knuth序列
h = 1;
h  = 3*h +1;
    

//希尔排序2
public static void shellSort2(int[] keys){
    int h = 1;
    while (h <= keys.length/3) {
        h  = 3*h +1;
    }

    for (int delta = h; delta > 0; delta = (delta-1)/3 ) {
        /**
         * 接下来就是进行在增量确定的情况下,进行组内的直接插入排序
         */
        //先进行分组后的第一组的排序,因第一组第一个元素下标为0,直接插入排序从第二个元素开始
        //并且其他组也是要从第二个元素开始进行直接插入排序,
        //所以此处i = delta,从下标为delta的元素开始,并且i++
        for (int i = delta; i < keys.length; i++) {
            //注意j的取值范围,要把每个元素都要比较,又要考虑数据越界
            // 所以j > delta-1 ,要把j - delta = 0这个情况也要想到
            for (int j = i; j > delta-1 ; j = j-delta) {
                if (keys[j - delta] > keys[j]) {
                    //交换
                    swap(keys, j, j-delta);
                }
            }
        }
    }
}
//经比较,采用Knuth序列的效率是要比数组长度一半然后折半的效率是要高的。

3.1、希尔排序算法分析

希尔排序算法的时间复杂度分析比较复杂,实际所需的时间取决于具体的增量序列。希尔排序的平均时间复杂度为n的1.3次方。即O(n^1.3)

希尔排序算法的空间复杂度为O(1)。

希尔排序算法在比较过程中,会错过关键字相等元素的比较,如上图的第1趟,将27*插入到前面的子序列中,则跳过关键字相等元素27,两者没有机会比较,算法不能控制稳定。因此,希尔排序算法不稳定

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值