初级排序算法2 Insertion Sort 插入排序

        插入排序就像小时候和朋友们打扑克时的摸牌的方法非常类似。记得那时候斗地主,很喜欢 小3 放左,小王大王放右边,从小到大的升序方式进行排序。从桌子上摸的第一张牌开始,我们可以把每一张牌当成一个待插入的元素:

第一次,摸到了一张牌,一个元素无法比较,不用排序;

然后一只手一张一张的摸牌,动态的保持这种升序排列,插入到另一只手上。

如此往复下去...

        新来的一张牌,首先,我们拿着这个尚未找到位置的待添加的纸牌,从右边这端开始,比较大小,如果小,继续向左运动,向左比。直到比手上展开的升序的”纸牌扇子“某个位置的牌大了,则插入该牌的右边。(看文字,不是很直观,冥想或者画个动画,更好理解。),插入的位置的左侧没比较的牌不用再看了,因为原来的就是有序的,左边的牌(当前找到的这个位置的)只能比牌更小,所以剩下的不用比较了。正是由于这个性质的影响,插入排序 Insertion sort 理论上对于一个很大且有序的数组,进行排序时候比 selection sort 选择排序要快一点。

类似这样的算法进行的排序,就是插入排序。

插入排序
插入排序 Insertion sort 

插入排序思想的实现:

        int [] arr = {5,4,1,3,2};

        for(int i = 1;i<arr.length;i++){
            for(int j = i ;j>0 ;j--){
                if(arr[j]<arr[j-1]){
                swap(arr,j,j-1);
                }else {
                    break;
                }
            }
        }

        System.out.println(Arrays.toString(arr));

不改变逻辑的情况下,优化一下代码

        int [] arr = {5,4,1,3,2};
        
        for(int i = 1;i<arr.length;i++){
        //改变 else break的部分,直接将条件提高到for的所在行中
            for(int j = i ;j>0 && arr[j]<arr[j-1];j--){
                swap(arr,j,j-1);
            }
        }

        System.out.println(Arrays.toString(arr));

我们可以把这种代码实现方式和小时候打扑克摸牌的整个过程联想在一起,其实是完全类似的。

对于第一个for-i 这个操作,当前的快照i 变量就是你摸在手上的牌(索引为i ),没有循环到的就是i后面的索引,就是放在桌子上堆起来的牌。因为i轮之前的几轮,牌都是有序的,所以呢, 这个  j从 i 开始,j--,逐个跟手上的 牌,以索引降序方式 去比较 较大的值,直到找到比手上牌 小(或相等)牌为止,比到的这张其实都比左边的大(因为它是已经排好顺序的),所以后面就不用比较了。  此时再去交换操作。

        但是你会发现这样的交换太多了,一次交换相当于三次赋值操作,这里有很多次频繁的操作,所以我们后面还要继续优化这个交换操作。

        在没有优化这个交换操作前,目前这个代码 并不影响 我们来做算法的分析的工作,我们主要围绕,以下2个方面 来进行分析:

1. 比较和交换的次数方面。

 2.额外的内存空间的移动开销方面。   

Insertion sort 步骤图示

      

        对于一个随机排序长度为n的数组,平均情况下,插入排序需要进行N²/4次比较和 N²/4次交换。

        最坏情况,需要进行N²/2次比较和 N²/2次交换。

        交换次数与输入数据有关,若输入数据刚好为目标顺序,则不需要交换,这种最好的情况,则最多需要n-1次比较(第一次不用比较,后面每次都要比较)。

        这些结论,可以通过上图有一条绿色元素连起来的对角线,并结合自己的冥想和数据分析,轻易获得。

        

插入算法再优化

        优化方向:为了减少不必要的交换的次数,我们可以再改变一下代码逻辑:



        int [] arr = {5,4,1,3,2};

        for(int i = 1;i<arr.length;i++){

            int e = arr[i]; //拿在手上的牌。
            int j ; //保持元素e 应该插入的位置。
            for( j = i ;j>0 && arr[j-1] > e;j--){
                arr[j] = arr[j-1];
            }
            arr[j] =e;
        }

        System.out.println(Arrays.toString(arr));

        通过代码,开始冥想:首先,这个变量e, 当作为本轮你操作对象——即是你手上拿着的,待插入的那张牌。而这个int j 作用是通过for寻找的定位(即j索引), 通过j-- 向左比较的for循环,一直比较,最终可以定位到所要插入的 位置 j;心动的瞬间是:当你发现 j 左侧的那个前面贴身的 [j-1] 位置元素,没有你手上的拿着的元素e 大的时候,那么你就可以把你手上拿着的e牌,放入到j位置即可,这个位置就是它适合的位置。而j位置原来元素不用担心丢失,因为在for比较的过程中,他们一直满足比较条件后,就会向右后挪动一位。(如果你实在冥想不出来,那就拿几张纸牌试试,人的脑细胞足够多,只要大脑功能还在,冥想应该是可以练习的。)

        至于这次的代码的优点是:减少了 swap 操作,毕竟在我们比较条件满足的时候,我们swap操作的底层 是进行三次赋值, 而这次只进行一次后移动 arr[j] = arr[j-1],后面的元素赋值给前面,也只不过是一次赋值而已。从而从性能上,有所提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值