本文介绍插入排序和希尔排序,插入排序是较为常见的排序算法,希尔排序也是基础的排序算法,废话不多说,具体来看一下两种算法。
插入排序
插入排序的基本思想是拿到下一个插入元素,在已经有序的待排数组部分找到自己的位置,然后进行数据的移动,完成该元素的排序,依次类推,直到整个待排数组有序,初始状态待排数组的有序部分仅有一个元素。代码如下:
public static void sort(int[] nums) { Arrays.nonBlank(nums); for (int i = 1; i < nums.length; i++) { for (int j = i; j > 0; j--) { if (nums[j] < nums[j - 1]) { int temp = nums[j]; nums[j] = nums[j - 1]; nums[j - 1] = temp; } } }}
上面这种方式两两数据交换,换到合适位置,每次交换多了两次赋值操作,下面是实现的标准模式:
public static void sort2(int[] nums) { Arrays.nonBlank(nums); for (int i = 1; i < nums.length; i++) { int temp = nums[i]; // 记录插入值 int j; for (j = i; j > 0 && temp < nums[j - 1]; j--) { nums[j] = nums[j - 1];// 寻找位置,能够减少赋值的次数 } nums[j] = temp;// 找到位置,完毕 }}
以上就是插入排序的两种实现方式,并没有改进,其时间复杂度为O(n^2),妥妥的两层循环,另外插入排序是稳定排序,存不存在一种改进的插入排序呢?答案是有的,这就是我们下面要介绍的希尔排序;
希尔排序
希尔排序是插入排序的改进版。主要改进思路是减少插入排序中数据的移动次数,设置步长,在初始数组较大时取较大步长,通常初始步长为待排数组长度1/2,此时只有两个元素比较,交换一次,此时数组为部分有序数组;之后步长依次减半直至步长为1,即为插入排序,此时数组已接近有序,所以插入元素时数据移动次数会相对较少,效率得到提高,实现代码如下:
public static void sort(int[] nums) { Arrays.nonBlank(nums); int N = 0 + nums.length; for (int gap = N / 2; gap > 0; gap /= 2) { for (int i = gap; i < N; i++) { insert(nums, i, gap); } }}private static void insert(int[] nums, int i, int gap) { int inserted = nums[i]; int j; for (j = i - gap; j > 0 && nums[j] > inserted; j -= gap) { nums[j + gap] = nums[j]; } nums[j + gap] = inserted;}
既然为改进算法,那么相比较插入排序,希尔排序的到底快了多少呢?为此特意去找了资料,一般书上都说希尔排序的时间复杂度是O(n^3/2),但是学过算法的都知道有最坏时间复杂度的,希尔排序的时间复杂度其实是和增列序列有关系的,即我们程序实现的步长,{1,2,4,8,...}这种序列就是我们程序中实现的这种,并不是很好的增量序列,使用这个增量序列的时间复杂度(最坏情形)是O(n^2),Hibbard提出了另一个增量序列{1,3,7,...,2^k-1}(质数增量),这种序列的时间复杂度(最坏情形)为O(n^1.5),这个提高就很厉害了,只是修改一个算法的一个参数;Sedgewick提出了几种增量序列,其最坏情形运行时间为O(n^1.3),其中最好的一个序列是{1,5,19,41,109,...},最后这个就当是科普小知识吧,因为给你你也不一定能用,请原谅我的直接,当别人说希尔排序的最坏时间复杂度是O(n^2)的时候,你也可以给他们科普一下,希尔排序的最坏时间复杂度是可以做到O(n^1.3)的。
总结
插入排序感觉是最为直观的排序方式,如果有人没有学过算法,人们最直接的排序方式就是,一个一个去找数字的位置,确定,然后再去找下一个,所以思想很简单,但是我们看到即时这样简单地排序思想,到了希尔这里,也能玩出花,所以对任何东西,尤其是简单的事物,都要心怀敬畏啊。
构成我们学习最大障碍的是已知的东西,而不是未知的东西。——贝尔纳