插入排序中的直接插入排序和希尔排序

老是学了就忘,打算开始写博客自己记录一下,相当于笔记吧。(基于尚学堂韩顺平的java数据结构视频总结)

一:插入排序中的直接插入排序:

   直接插入排序,是最基本的插入排序算法,个人感觉就和打牌一样,手里的牌分为两块,一块有序,往往放在左边,另一块是无序的,需要把每张去慢慢调整,所以就做起了把每张牌向左边有序块插入的动作。

举个例子:比如数组{101,34,119,1}

   第一次插入:因为要左边有序,所以默认假设数组第一位后的元素是无序的,从数组第二位开始,取第二位34,与第一位101作比较,发现小于,所以插入后是,34,101,119,1,有序块增加一位;

   同理第二第三次,每次都与前面的有序块作比较,第二次插入:取119比较前面的有序块,34,101,119,1; 第三次插入:取1,与前面的有序块的每一位挨个比较,(此处的规则在下面说明),总之插入完后,结果为1,34,101,119那么问题来了,需要哪些变量?

   通过上面说明,发现需要待插入元素insertVal,待插入的位置insertIndex,但是待插入元素的位置又不好确定,通过例子,发现待插入元素每一次开始比较都要和前一个元素(即有序块的最后一个元素)比较,那么可以默认insertIndex的位置时待插入元素的前一个元素的位置。那如果比较后要插入,那咋办啊?(此处是直接插入排序的核心)

  可以用移位法,比如第一次插入,待插入元素刚好比前一个元素小,可以先用中间变量保存indexVal,然后把前一个元素后移,即往后复制一位实现后移,最后把中间变量赋给待插入的位置。那么如果有序块里有若干个元素呢,则用循环,如果符合判断条件,待插入位置后面的有序块的元素都要往后移一位, 使用while循环,但循环执行完,说明待插入的位置已经找到,但是此处也体现了插入排序的缺点,当插入数据量少时,效率慢。

以下是移位的代码块:

         int insertVal;  //待插入的数
        int insertIndex; //待插入的下标,待插入数的前一位
     
            //insertIndex>=0 :防止数组越界   insertVal<arr[insertIndex]:若待插入的数比前面的小,则将前面的数后移
            while (insertIndex>=0 && insertVal<arr[insertIndex]){
                arr[insertIndex+1]=arr[insertIndex];
                insertIndex--;  //每一次后移完,则下标减一,再与前面的数比较
            }
     
                //当退出while循环时,说明插入的位置已经找到
                arr[insertIndex+1]=insertVal;
            
        }

移位的问题解决后,插入排序也就大体完成了,代码如下:

 public static void insertSortExample(int[] arr){
        int insertVal=0;  //待插入的数
        int insertIndex=0-1; //待插入的下标,待插入数的前一位
        for (int i=1;i<arr.length;i++){ //插入n-1次
            insertVal=arr[i];
            insertIndex=i-1;
            //insertIndex>=0 :防止数组越界   insertVal<arr[insertIndex]:若待插入的数比前面的小,则将前面的数后移
            while (insertIndex>=0 && insertVal<arr[insertIndex]){
                arr[insertIndex+1]=arr[insertIndex];
                insertIndex--;  //每一次后移完,则下标减一,再与前面的数比较
            }
            //判断是否需要赋值  ,代码优化
            if((insertIndex+1)!=i){
                //当退出while循环时,说明插入的位置已经找到
                arr[insertIndex+1]=insertVal;
            }
        }
      //  print(arr);
    }

二:希尔排序(优化过的直接插入排序,又称缩小增量排序)

  对于插入排序的问题,如果有序块的元素逐渐增多,那么待插入元素的比较次数就会增多,效率变慢,因为有序块只有一个,也只能慢慢挨个元素比较。

  希尔排序的优化在于,可以进行分组排序,即把数组按一定规则(即按增量(按一定数组间距))分组,然后分组进行排序,排完后缩小增量,也就缩小了间距,可以渐渐合并为一个数组,完成排序。

个人能力有限,解释不清,看例子吧:

举例:{8,9,1,7,2,3,5,4,6,0}

说说增量怎么取,默认每次/2,取得增量,即增量gap=gap/2,gap默认为数组长度。

(1)第一次,增量为5,说明分组按数组的间距为5分,分完后:{8,3},{9,5},{1,4},{7,6},{2,0},共五组,然后先使用交换法比较,即对应增量的两数比较交换。

注意,虽然分组,但是是虚拟组,并不存在,都是按数组下标-5分组的(因为代码中从第6位开始的),代码如下:

int temp=0;
        //第一轮轮排序:将十个数据分为了10/2=5即(增量为5的)五组,(8,3),(9,5)(1,4)(7,6)(2,0)
        for (int i = 5; i <arr.length ; i++) {
            //遍历各组循环中的所有元素(共五组)  步长为:5
            for (int j = i-5; j>=0 ; j-=5) {
                if(arr[j]>arr[j+5]){  //如果前面的大于后面的
                    temp =arr[j];
                    arr[j]=arr[j+5];
                    arr[j+5]=temp;
                }
            }
        }
        System.out.println("第一轮希尔排序后:"+toString(arr));

比较完成后,此数组为:{3,5,1,6,0,8,9,4,7,2},若有疑惑,根据上面的增量即下标+5对比看

(2)第二次,增量为5/2=2,即间距为2,分完后:{3,1,0,9,7},{5,6,8,4,2}

同理,代码如下:

 //第二轮:将五组数据继续分为增量为5/2=2的两组
        for (int i = 2; i <arr.length ; i++) {
            //遍历各组循环中的所有元素(共两组)  步长为:2
            for (int j = i-2; j>=0 ; j-=2) {
                if(arr[j]>arr[j+2]){  //如果前面的大于后面的
                    temp =arr[j];
                    arr[j]=arr[j+2];
                    arr[j+2]=temp;
                }
            }
        }
        System.out.println("第二轮希尔排序后:"+toString(arr));

执行完成后,此数组为:{0,+2, 1,+4, 3,+5, 7,+6, 9,+8}   ,+用来标注一下,便于理解。

(3)第三次:增量为2/2=1,说明间距为1,也就是就一个数组,即{0,+2, 1,+4, 3,+5, 7,+6, 9,+8},则比较,得出排序好的数组,代码如下:

 //第三轮:将两数据继续分为增量为2/2=1的一组
        for (int i = 1; i <arr.length ; i++) {
            //遍历各组循环中的所有元素  步长为:1
            for (int j = i-1; j>=0 ; j-=1) {
                if(arr[j]>arr[j+1]){  //如果前面的大于后面的
                    temp =arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        }
        System.out.println("第三轮希尔排序后:"+toString(arr));

通过上述(1)(2)(3),发现增量也是分的组数(因为增量即间距),而且增量gap也可作循环的判断条件,整合代码如下:

 int temp=0;
       for (int gap=arr.length/2;gap>0;gap/=2){
           //遍历各组循环中的所有元素(共gap组)  步长为:gap
           for (int i = gap; i <arr.length ; i++) {
               for (int j = i-gap; j >=0 ; j-=gap) {
                   if(arr[j]>arr[j+gap]){
                        temp=arr[j];
                        arr[j]=arr[j+gap];
                        arr[j+gap]=temp;
                   }
               }
           }
       }
        System.out.println("希尔排序后:"+toString(arr));

 但是发现一个问题,使用交换法需要使用三层for循环,比较笨重,于是可以改进,使用移动法即直接插入排序代替交换法,需要注意的是,此时待插入的位置可不是从待插入元素的前一个位置开始,应该为增量(因为按增量分组的),为了方便,默认为待插入元素的下标,代码如下:

 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[i]; //待插入的数
                if(arr[j-gap]>arr[j]){
                    while (j-gap>=0 && temp<arr[j-gap] ){
                        //移动
                        arr[j]=arr[j-gap];
                        j-=gap;
                    }
                    arr[j]=temp;
                }
            }
        }
        System.out.println("希尔排序后:"+toString(arr));

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值