浅谈插入排序

浅谈冒泡排序

上一篇文章中讲解了冒泡排序,冒泡排序无论是最好、最坏还是平均情况下时间复杂度都是O(n²),而且是原地稳定的排序算法。这篇文章中将会介绍另一种时间复杂度为O(n²),与冒泡排序同为原地稳定的排序算法–插入排序。我们可能会产生这种疑惑,各项指标都一样,我们为什么要引入插入排序算法呢?下面我们带着问题来进入今天的讲解

插入排序的思想如下:
我们要把6这个元素插入到一个数组中,首先需要根据元素之间的大小找到元素6要插入的位置,然后把这个位置之后的7、9、11、13这几个元素一次向后挪动一个位置,最后把6这个元素插入进去,这样就完成了一次插入排序。
在这里插入图片描述
插入排序是一个动态排序的过程,即动态的往有序集合中添加元素,我们可以通过这种方法保持集合中的元素一直有序。而对于一组静态数据,上面提到的插入方法也十分有效。

插入排序的具体过程
首先,我们数组分成两个区间,已排序区间未排序区间。初始的数组的已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想就是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程直到未排序区间中元素为空,算法结束。
如下图所示,要排序的数组中的元素为4、5、6、1、3、2,其中左侧为已排序区间,右侧是未排序区间。
在这里插入图片描述
插入排序同冒泡排序一样包含两种操作,一种是元素的比较,一种是元素的移动。当我们需要将一个数据a插入到已排序的区间时,需要拿a与已排序的区间的元素一次比较大小,找到合适的插入位置。找到插入点之后,我们还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给a插入。具体代码如下:

//插入排序方法,a为要排序的数组,n为此数组的长度
    public void insertionSort(int[] a ,int n){
        //如果数组中只有一个元素,那么直接返回,无需排序
        if(n <= 1) return;

        //第一个元素为已排序区间,我们从第二个元素未排序区间开始进行循环
        //i从未排序区间的第一个元素开始
        for(int i = 1 ; i < n ; i++){
            //要插入的元素
            int value = a[i];
            //j从已排序区间的最后一个元素开始
            int j = i - 1;
            for(;j >=0;--j){
                //如果要插入的元素小于此元素,则此元素后移一位
                if(a[j] > value){
                    a[j+1] = a[j];
                }else {
                    break;
                }
            }
            //由于循环结束时,j会减1,所以实际插入的位置为a[j+1]
            a[j+1] = value;
        }
    }

一、插入排序是原地排序算法吗?
从代码中可以很明显的看出,插入排序算法的运行并不需要额外的存储空间,所以算法的空间复杂度为O(1),即插入排序是原地排序的算法。

二、插入排序是稳定的排序算法吗?

if(a[j] > value){
a[j+1] = a[j];
}

从代码中我们可以看出,发生移动的条件是要插入的元素比此位置的元素要小,而不是小于等于。也就是说如果两个元素相等,已排序区间的元素不会发生移动,这就保证了要插入的元素在之前相同元素之后插入,从而保证的算法的稳定性。

三、插入排序的时间复杂度是多少?
最好情况下要排序的数组已经有序,那么我们无需进行插入操作,每个元素只需要与自己进行一次比较操作就可以确定要插入的位置,所有最好情况下时间复杂度为O(n)
最坏情况下,如果数组是倒序,每次插入都相当于在数组的第一个位置插入新的数据,所以每次插入数组都要移动插入位置之后的数据,所以最坏情况下的时间复杂度为O(n²)
而平均时间复杂度下,我们可以将最好和最坏情况时间复杂度进行相加除以2,即**(n+n²)/2**。当n足够大的时候,只有才会起决定性作用,所以平均时间复杂度也为O(n²)。

最后我们来说一下文章开头所提出的问题,插入排序比冒泡排序好在哪里。
我们从代码角度来看

冒泡排序

for(int j = 0 ; j < n-i-1;j++){
                //如果前一个元素比后一个元素大,交换
                if(a[j] > a[j+1]){
                    int tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;
                    //由于发生过交换了,标志改为true
                    flag = true;
                }
            }

插入排序

for(;j >=0;--j){
                //如果要插入的元素小于此元素,则此元素后移一位
                if(a[j] > value){
                    a[j+1] = a[j];
                }else {
                    break;
                }
            }

从代码中可以很明显的看出,冒泡排序的第二次循环中,交换操作的代码发生了三次赋值的操作,而插入排序的第二次循环代码只发生了一次赋值操作。于是乎我们可以得出结论。虽然两种排序算法的时间复杂度都为O(n²),但是插入排序比冒泡排序节省了两次赋值的时间,所以在实际运用中插入排序要比冒泡排序更受欢迎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值