Java代码实现:插入排序(直接插入,折半插入,希尔排序)

1、直接插入排序((insertSort)
  • 首先思考一个问题,往一个已经排好序的数组中插入一个数字,插入后数组依旧是有序数组,这是一个动态排序的过程,我们可以借鉴这种插入方法来实现排序,所以就有了插入排序;
  • 所谓直接插入排序,就是将待排序的数组中的元素分成两个区间,已排序区间和未排序区间,刚开始时将数组的首元素作为已排序区间的第一个元素,数组中的其余元素为未排序区域,然后每次取未排序区间的元素在已排序区间找到合适的位置插入,同时保证已排序区间数据一直有序。并且重复这个过程,直到未排序空间为空,排序结束;
    插入方法如图所示:
    插入后没有影响原数组的有序性
    下面看一下直接插入排序的图示,自己画的演示图,感觉还行
    在这里插入图片描述
直接插入代码:
    /**
     * 直接插入排序;
     * @param data
     */
    public static void insertSort(int[] data){
        long start = System.currentTimeMillis();
        int[] dabs = data;
        int n = dabs.length;
        if (n <= 1){
            return;
        }else{
            for (int i = 1;i < dabs.length;i++){
                int value = dabs[i];
                for (int j = i-1;j >= 0;j--){
                    if (dabs[j] < value){
                        dabs[j+1] = value;
                    }else{
                        dabs[j+1] = dabs[j];
                        if (j == 0){
                            dabs[j] = value;
                        }
                    }
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("直接插入排序耗时"+(end-start)+"毫秒");
    }
2、折半插入排序

折半插入排序(binary Insertion Sort)就是对前面插入排序算法的一种改进,就是将未排序区域的元素插入到已排序区域的时候,寻找合适位置的方法是二分查找法,就是说,每次都找的是当前区间的中点元素,中点元素将已排序区间划分成两部分,小于它的区域(左半部分)和大于它的区域(右半区域),如果要插入元素大于中点元素,那么就在右区域继续这样寻找,左区域同理;直到最后的区域只剩下一个元素,比较后直接插入这最后一个元素的前面或者后面;

  • 二分查找具体实现方法,三个指针下标,left\mid\right,开始的时候,left指向当前数组的最左边,mid为中间下标的元素,right为最右边元素的下标,当待插入元素跟mid元素比较一次之后,如果大于mid,就继续在右边区域查找,left重新指向mid+1处的元素;如果小于,right指向mid-1处的元素;然后继续跟接下来的区间mid元素比较,直到区间剩下最后不能再划分左右区间的时候停止;

下面是折半查找方法的示意图:
在这里插入图片描述

上面图片中所说的要注意的两种插入位置临界情况,这里具体说明一下:
  1. 当待插入元素大于mid元素时,会有left = mid+1,此时left指向数字4这个元素,此时left > right,跳出循环,那么插入位置的下标是?可以由图片的处下标是元素4的下标,也就是说此时left就是要插入的位置,只需要把包括4往后的元素往后移动一个单位即可,然后插入3;
  2. 可是如果此时要插入的元素value < mid,这时候right=right-1,此时left>right,跳出循环,这时right指向的是mid元素1前面的位置,而left指向的是1,由于1已经是数组的第一个元素,如果要插在1的前面,就需要将1以及后面的所有元素往后移动,注意,此时要插入的位置下标依旧是left所指向的位置;
  3. 综上,当left>right的时候,也就是说跳出循环的时候,left下标一直是所要插入的位置的下标;
代码举例如下,实际上就是改了一下查找合适位置的方法:
 /**
     * 折半插入排序;
     * 这种二分插入的方法,就是二分查找跟直接插入的结合
     * 相比以前的直接插入方式,二分法明显节省了一半时间;
     * 直接查找到要插入的位置,然后直接插入;
     * @param data
     */
    public static void binaryInsertSort(int[] data){
        long start = System.currentTimeMillis();
        int[] dabs = data;
        int n = dabs.length;
        if (n <= 1){
            return;
        }else{
        //这里是重点,二分查找方法
            for (int i = 1;i < n;i++){
                int left = 0;
                int high = i-1;
                int value = dabs[i];

                while(left <= high){
                    int mid = (left+high)/2;
                    if (dabs[mid] < value){
                        left = mid+1;
                    }else{
                        high = mid-1;
                    }
                }
                //循环结束后mid=left=high;
                //下面就要给mid的位置插入value;
                int j ;
                for ( j = i-1; j > high;j--){
                    dabs[j+1] = dabs[j];
                }
                dabs[j+1] = value;
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("折半插入排序耗时"+(end-start)+"毫秒");
    }

3、比较一下直接插入和折半插入分别在数据集有序跟无序的情况下的耗时:
  1. 近乎有序的情况下:

在这里插入图片描述
在这里插入图片描述
2. 无序状态下两者对比:
在这里插入图片描述在这里插入图片描述

  1. 小结:折半插入排序在数据集无序的情况下要优于直接插排;但是在近乎有序的数据集下,因为直接插排只比较一次,此时优于折半插排;因此最好情况下的直接插入排序要优于折半插入排序;
4、插入排序小结:(同直接插排跟折半插排)
  • 插入排序的算法运行并不需要额外的存储空间,所以空间复杂度是O(1),所以说是个原地排序算法;
  • 稳定性判别,对于值相同的元素,我们可以将后面出现的元素插入到前面出现元素的后面,从而保持了原有的前后顺序不变;否则就不稳定;
  • 时间复杂度,最好情况就是待排序的数组已经排好序,所以每次比较的时候只需要比较一个元素就能确定要插入的位置,这种情况下时间复杂度为O(n),可是当数组顺序刚好全是倒序,那么每次插入都相当于在数组的第一个位置插入,需要移动大量的元素,此时的时间复杂度是O(n^2);所以平均复杂度是O(n ^2);
5、希尔(Shell)排序(又叫缩小增量排序)

希尔排序就是简单插入排序的优化:
优化:原先找到插入位置后,元素是一个一个向后搬移,损耗较大;
能否在搬移元素时多走几步,即一步移好几个元素;
原先是一次走一步,元素是一个一个往后搬,损耗较大;现在是一次走好几步;

  • 举个例子:就是说,八个数字,先规定差距为四的数字比较,就是第一个跟第五个比较,第二次循环,两个数字为差距,比较,就是第一和第三个数字比较;第三次循环就是相邻的两个元素相比较了;结束条件就是距离为0个元素时;
  • 我先画个简单的示意图:
    在这里插入图片描述
    图片中第三轮排序时step=1,完了之后依旧排序完成,所以终止条件是step=0;
代码如下
/**
     * 希尔排序,就是把一串数据依次分成从大到小的模块
     * 每次采用插入排序法排完区块里的数据后再逐渐减少模块的大小
     * 知道模块为单个元素截止;
     * @param data
     */
    public static void shellSort(int[] data){
        long start = System.currentTimeMillis();
        int step = data.length/2;
        while (step != 0){
            int n = data.length;

            for (int i = step;i < n;i++){
                int tmp = data[i];
                int j = i - step;
                /*while(j >= 0 && data[j] > tmp){
                    data[j+step] = data[j];
                    j = j -step;
                }*/
                for (;j >= 0;j -= step){
                    if (data[j] > tmp){
                        data[j+step] = data[j];
                    }else{
                        break;
                    }
                }
                data[j+step] = tmp;
            }
            step = step/2;
        }
        long end = System.currentTimeMillis();
        System.out.println("希尔排序耗时为"+(end-start)+"毫秒");
    }

5.1、希尔排序小结一下:

稳定排序;第一个突破O(n^2)的排序算法,平均复杂度在O(nlog n)~O(n ^ 2)之间;

到此插入排序的小心得小体会到此位置,后面会继续补充完善

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值