排序算法3:插入排序,二分插入排序

  • 简介

插入排序是一种与冒泡、选择排序略微有些不同的排序算法。它的主要思想可以形象的模拟为打牌时从牌堆中抓牌后,整合入手牌中的过程。它的时间复杂度为O(n^2),最好情况下可以达到O(n)。空间复杂度为O(1),它是一种稳定的算法。

  • 思想

我们前面讲的冒泡和选择排序它们都是外层使用for循环中的变量i来维护已排序的数字,然后在未排序数列中进行操作(冒泡是直接交换,选择是选出需要交换的元素)。插入排序则是侧重于在有序数列中的操作。

排序开始,我们从牌堆中抽第一张牌放在左手,左手就是已排序数列,第一张牌(arr[0])就默认为它的第一个元素。接下来再次从牌堆顶拿牌,我们抽到哪个牌就是哪个,并不能翻看底下牌堆的牌,所以在算法中也是保持了这种思想,每次都从未排序数列中取第一个数,再将它和已排序数列(左手已整理好的手牌)从后向前挨个比较,找到合适的位置之后进行插入,完成一趟排序。

  • 具体代码(数组实现,升序)
void InsertSort(int arr[],int len)
{
    for (int i = 1; i < len; i++)
    {
        int get = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > get)
        {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = get;
    }
}

可以看到,其中get变量保存的就是每次从牌堆最上方抽取到的一张牌,进入while循环就是为了找到合适的位置,最后退出while循环执行插入操作。

 

  • 插入排序的改进:二分插入排序

想必大家都听说过二分法。在查找数据的时候我们有时候会用到它。在我们插入排序的过程中,有一步是需要将未排序队列的第一个数字放入到有序数列中的合适位置,而普通的插入排序的做法就是从后向前依次进行比较。如果我们需要升序排序而数组正好是完全降序(即最坏情况),此时每一趟都要从最后比较到开头,效率十分低下。或者当len特别大的时候,比较的代价已经超过了交换的代价,针对这两种情况我们将二分法运用到查找位置这一步,将大大提高最坏情况下的效率。

  • 具体代码
void SelfInsertSort(int arr[], int len)
{
    for (int i = 1; i < len; i++)
    {
        int get = arr[i];
        int left = 0;
        int right = i - 1;
        while (left <= right)
        {
            int mid = (left + right) / 2;
            if (arr[mid] > get)
            {right = mid - 1;}
            else
            {left = mid + 1;}
        }
        for (int j = i - 1; j >= left; j-- )
        {
            arr[j + 1] = arr[j];
        }
        arr[left] = get;
    }
}

每趟排序先定义左右边界(左边界为0,右边界是i-1),同样定义get来保存待插入的数据。进入while循环后进行比较,如果arr[mid]比get大说明在arr[mid]的左边,将右边界变为mid-1;如果arr[mid]比get小说明在arr[mid]的右边,将左边界变为mid+1。当不满足条件left <= right时已经确定好了位置。我们将以left作为基准。随后将left到队尾的数据全部一次性右移1位,给get腾出位置。最后进行插入。

我们之前说了,在完全有序或接近有序的情况下这个算法的时间复杂度确实是比普通插入排序低的,为O(nlogn)。但是在大部分有序或者完全有序的情况下,我们知道直接插入排序在这种情况下时间复杂度为O(n),通过代码可以看出根本不会进入while循环。但是二分插入排序仍然需要进行位置的确定,通过反复计算arr[mid]与get的关系才能确定位置。此时的二分排序时间复杂度为O(n^2),反而落后普通插入排序一大截。

需要注意的一点是,它们的时间复杂度区别只在于比较的次数不同,它们交换的次数是完全相同的。

所以具体使用的时候,我们还是要根据待排序数组的特性来判断选择合适的算法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值