一.介绍
插入排序中有比较简单的直接插入排序,也有在直接插入排序上升级版本:希尔排序.
插入排序(insertion sort)的基本思想是:每趟将一个元素,按其关键字大小插入到它前面已排序的子序列中,使得插入后的子序列仍是排序的,依此重复,直到全部元素插入完毕.
二.详解
1.直接插入排序
思想指导方法:首先我们要找到一个已经排序好了的子序列,而一开始最佳选择就是在第一个位置的元素(单个元素也就是排序号的子序列啦).然后让接下来一个元素和前面已经排序好了的子序列进行比较,而比较一般是从子序列中的最后一个元素开始比较的,原因有是我们要把已经空出来的位置(即插入的元素的原位置,此时已经被一个临时的temp值保存下来了),让给已经排序号的子序列的最大值,也就是在最后面的位置.而且,以后的每次比较,只要比插入值(temp)大,我们就把这个元素移动前面已经空好的位置.当某个值比插入值小或者相等的时候,就可以把前面已经移走元素的空位置,让temp填入.不断循环,知道所有的元素都被插入为结束的标志.
public static int[] insertSort(int[] table){ for(int i =1; i<table.length;i++){ int temp = table[i]; int j; for(j = i-1;j>= 0 && temp < table[j]; j--){ table[j+1] = table[j]; } table[j+1] = temp; } return table; }
我们分析下这个代码:i的初始值为1而不是0,原因是我们默认第一个元素(即下标为0的元素)为排序好的子序列,而结束标志是所有的数组中的元素都被插入.i的增量为每次加1.
temp即为我们要插入的元素,j的初始值为子序列中的最后一个元素的下标,然后不断循环j减小,每次都把比temp大的值向前移动一位,当然此时被填充的位置的值已经对于我们没有意义了,因为这个元素不是被以temp的形式保存就是移动到更加前面的一个位置了.每次插入循环结束的标志就是有两个,一个是j的位置到了最前面,一个是temp的值比被比较的值大,这就是结束的标志.
接下来我们来分析下直接插入排序的时间复杂度和空间复杂度.
<1>数据序列已排序(最好情况)的时间复杂度为O(n).若一个数据序列已排序号,如{1,2,3,4,5,6},每趟中a (i) 与 a (i-1) 比较一次,移动次数为2(table[i]到temp再返回).总比较次数为n-1,总移动次数为2(n-1).因此,直接插入排序算法在最好情况下的时间复杂度是O(n).
<2>数据序列反序列排序(最坏情况)的时间复杂度为O(n^2).若一个数据序列反序排序,如{6,5,4,3,2,1},第i趟中插入元素a (i) 的比较次数为i,移动次数为i+2.总的比较次数C和移动次数M都为(1+2+3+...+i)和(3+4+5+...+i+2),积和为n(n-1)/2和(n-1)(n-4)/2,时间复杂度为O(n).
<3>数据序列随机排序 若一个数据序列随机排列,第i趟插入元素a(i),在等概率的情况下,在前i个元素组成的子序列中查找一个元素(和temp值小,且差值最小)的平均比较次数为(i+1)/2,插入一个元素的平均移动次数为(i+4)/2.
总之,直接插入排序的时间复杂度为O(n^2).数据序列的初始排列越接近有序,直接插入排序的时间效率越高,其时间效率在O(n)到O(n^2)之间.直接插入排序的空间复杂度为O(1).
2,希尔排序
由来,可以说希尔排序是直接插入排序的升级版,在直接插入排序上面包装了一层东西,而这层东西恰恰是使得直接插入排序时间复杂度变得更为小.由我们上面直接插入排序的规律中可以得出,只要排序的初始排序越接近有序,而且初始排序的长度越小,则时间效率越高.
所以我们按照上面的规律把数组序列变得相对更加小,更加有序,时间效率则可以提高很多啦.
希尔排序的算法描述如下:
<1>将一个数据序列分为若干组,,每组有若干相隔一段距离的元素组成,这段距离称为增量,在一个组内采用直接插入排序算法进行排序.
<2>增量的初值通常为数据序列的一半,以后每趟增量逐渐缩小,最后值为1.随着增量逐渐减小,组数也减少,组内元素个数增加,整个序列则接近有序.当增量为1时,只有一组,元素是整个序列,再进行一趟直接插入排序即可.
public static void shellSortBook(int[] table){ //shellSort的做法 for(int delta = table.length/2; delta > 0; delta /=2){ //若干趟扫描,控制增量,增量减半 for( int i = delta; i < table.length; i++){ //一趟分若干组,每组进行直接插入排序 int temp = table[i],j;//table[i]是当前待插入元素 for(j = i - delta; j >= 0 && temp < table[j]; j -=delta){ table[j+delta] = table[j];//每组元素相距delta远,寻找插入位置 } table[j+delta] = temp;//插入元素 } } }
分析:希尔排序算法的三重循环:
a.最外层循环for语句以增量delta变化控制进行若干趟扫描,delta处置为序列长度n的一般,以后逐趟减半,直至为1.
b.第二个for循环中一开始会比较模糊,不是说好了按照delta增量的距离来分组的话进行直接插入排序的吗?怎么还是这样一锅粥乱炖,还是i到table.length才结束的呢,其实我一开始就在想,如何把数组中的数按照delta的距离来分组排序,难道要为它们新建table.length/delta个小数组,再让它们在各自的小数组里面进行直接插入排序吗?太耗费空间了吧.我一开课本的做法,吓了我一跳,原来元素下标做手脚就可以啦.在直接插入排序中我们的增量其实一直都是1,如今我们的增量每次都在随着最外层循环的变化而减小的.所以在第二个for循环中j的变化单位不是1而是delta.如果我们把所有j(i-delta)以后的数都以delta分开来看不就是table.length/delta个小组在各自进行着直接插入排序吗?
c.第三个for循环中其实和直接插入排序的核心代码一样,就不解释了.
分析时间复杂度和空间复杂度:
希尔排序算法的时间复杂度分析比较复杂,实际所需的时间取决于具体的增量序列.希尔排序算法的空间复杂度为O(1).希尔排序算法不稳定.