排序算法(二)-插入排序

687 阅读3分钟

插入排序

屏幕录制2022-04-02 上午9.48.37.gif

插入排序执行流程:

  • 在执行过程中将序列分为已经排序好的和未排序的两部分,头部是已经排好序的,尾部是待排序的
  • 从头开始扫描每一个元素,将它插入到合适的位置,使头部依然保持有序状态

代码实现:

image.png

数据测试:

image.png

插入排序中,如果逆序对数量越多,那么插入排序的时间复杂度就越高,逆序对指的就是数据中逆序的对数,例如数据{5,4,3,2,1}中(5,4)、(5,3)、(5,2)、(5,1)、(4,3)、(4,2)、(4,1)、(3,2)、(3,1)、(2,1)都是它的逆序对。

设想一个完全逆序的数组{n,...,5,4,3,2,1},比较的次数大约为:1+2+3+4+...+(n-1),时间复杂度大约是n2n^2级别

插入排序优化1.0

在之前的做法中我们是依次从后面拿到数据后与之前有序的数据依次比较,如果比之前有序的数据小则进行交换,现在优化的点在于把交换替换为挪动

image.png

如图所示,之前的做法流程是与之前有序的数据依次对比比它大则两者进行交换直到发现比它小的则停止,现在的思路是找到合适插入的位置后把该挪动的数据进行挪动即可,挪动完成后将数据插入到合适的位置,虽然比较的次数没有变少,但是原来交换的代码是三行,挪动只需要一行代码,在这里实现了优化。

代码实现:

image.png

数据测试如下图,这次的优化效果还是能看的出的,但是这次只是将交换优化成了挪动,但是在查找前面有序的时间复杂度依然是O(n)O(n),既然前面已经是有序的,可以考虑使用二分查找来进行优化,从而降低时间复杂度

image.png

插入排序优化2.0

前面提到使用二分查找来优化,既然如此,先来了解什么是二分查找

二分查找

首先提出一个问题:怎样来确定一个元素在数组中的位置?

  • 假设一个数组中的数据是无序的,那么从头开始遍历来查找,时间复杂度为O(n)O(n)

  • 但是如果数组是一个有序数组,采用二分查找,最坏时间复杂度为O(logn)O(log_n)

二分查找又称为折半查找,思路:

  • 假设在[begin, end)范围内搜索某个元素v,令mid = (begin + end) / 2
  • 如果v < mid,则对[begin, mid)范围继续使用二分查找
  • 如果v > mid,则对[mid + 1, end)范围继续使用二分查找
  • 如果v == mid,直接返回mid

代码实现:

image.png

查找过程如图所示:

image.png

通过二分查找来优化插入排序

刚才已经科普了二分查找的具体步骤,那么言归正传,现在需要用二分查找来优化查找需要插入的位置,但是寻找插入位置的方法又与之前直接使用二分查找找到元素索引的过程略有不同

如下图所示,使用二分查找来找到插入元素v的位置: image.png

  • v = 6,返回1
  • v = 30,返回8
  • v = 10,返回5
  • v = 3,返回0

其实就是要求找到第一个比v大的元素的位置

思路: 假设在[begin, end)范围内搜索元素v,mid = (begin + end) >> 1

  • v < mid,则在[begin, mid)范围内继续使用二分查找
  • v >= mid,则在[mid + 1, end)范围内继续使用二分查找

代码实现:

image.png

数据验证:

image.png

总结:

使用了二分查找优化后的插入排序只是减少了比较的次数,但是平均时间复杂度依然为O(n2)O(n^2)