首先我们引入一张思维导图,并且以后的排序博客我将以这位博主的博文为基础,进行代码自我实现,简单介绍以及自我理解。
链接:cnblogs.com/cndarren/p/11787368.html。
这篇博客主要介绍简单插入排序和希尔排序的思想以及自己的实现。
简单插入排序
简单插入排序的思想非常容易理解,就是打扑克时的调整排序。
假如手中有一副牌【1,2,3,1,4,5,2,J,K,3】
最后我们需要把牌调整为从大到小的顺序。
所以我们的操作都是把 1 放到最前面,把 2 放到 第一个2 的后面,再把3放到第一个3的后面,也就是直接插入到某个位置.
我们模仿一下把1放到最前面的操作。
我们来抽象扑克调整的思路,从任意的一个位置( I )开始从前向后找(从 0 到 I 的位置),找到比它大的位置( J )。先让 J 到 I - 1 的这段牌向后移动,然后把,I位置的这张牌,插入到 J 位置(因为J位置的牌向后移动了一个单位,所以 J 位置空缺)。
那么我们来实现一下。
public static int[] InserSort(int[] arr){
if(arr.length < 2 || arr == null){
return arr;
}
for(int i = 1;i < arr.length;i++) { //控制躺数,从第二张牌(也就是1号下标)开始以边遍历到最后
int temp = arr[i]; //先保存当前下标的值,从前向后遍历到当前位置,
// 找到第一个比自己大的位置,先让目标位置 到 当前位置-1的数字全部向后移动一个单位,再放入temp;
int j = 0;
while( j < i ){
if(arr[j] > temp){
break; //当我们找到目标位置的时候我们跳出处理。
}
j++;
}
if(j != i){ //有可能遍历到当前下标之后没有找到目标位置,也就是没有找到比自己大的数,i之前的序列全部有序,并且 i 比 i-1 大
for(int x = i ;x > j ;x--){
arr[x] = arr[x-1]; //j 下标到 i-1 下标全部向后移动一个单位。
}
arr[j] = temp; //在目标位置放入temp
}
}
return arr;
}
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
优化:
我们可以发现无论是最好情况还是最坏情况,我们都是从前向后遍历,导致我们的时间复杂度一直是O(n^2);那么我们能不能把 移数字和放数字合在一起呢?既然当前位置的前面的数字已经有序,我找到比自己小的那个位置的后一个插入,不就好了,如果从后向前找,那么如果 前一个数字就比自己小,自己不就可以不用移动了,那么针对我们的最优序列就可以这么去优化了!
优化实现:
public static int[] InsertSort1(int[] arr){
if(arr.length < 2 || arr == null){
return arr;
}
for(int i = 1 ; i < arr.length ; i++) { //控制趟数。从第二个数字开始插入排序
int temp = arr[i];
int j = i;
while( j > 0 && arr[j-1] > temp){ //从当前位置开始向前寻找
arr[j] = arr[j-1];
j--; //如果比自己大,那就让前一个数字覆盖自己(向后移动)
}
if(j != i){ //先判断一下如果目标位置与开始位置不同就证明找到了
arr[j] = temp; //找到自己比小的位置了,在其后直接放入temp
}
}
return arr;
}
如此一来,我们的最优情况下时间复杂度就可以降低置 O(n) 。
由此,我们可以看出插入排序的特性,越有序的序列排序越快。
额外知识:Java底层排序运用的是原始归并排序,以及基于多路归并排序实现的TimSort,还有双轴快排,在使用双轴快排时,还是使用到了双插排序,也就是两个数字进行插入排序,算的上是插入排序的一种优化。
思想如下:
在数组中选择两个数,一个数大一个数小,先讲小的数进行插入排序,小的数插入完成后,大的数只需要从小的数插入的位置之后开始寻找插入的位置即可。
在TimSort中还运用到了二分插入排序
二分插入排序顾名思义,在插入时运用到了二分查找的思想,原始的插入排序的思想是,从当前位置向前一个一个寻找比自己小第一个位置,然后插入,而二分插入的思想值直接对当前位置之前的有序序列进行二分查找,这样找位置的时间复杂度会从 O(n)降低到 O(log n) 级别。
希尔排序
希尔排序是基于插入排序实现的,由于插入排序的特新那个,在希尔排序中引入了 gap 这个参数,他将我们的长数组利用 gap 这一间隔,分为了几个小数组,对这些小数组进行直接插入排序后,此时序列就相对变得有序一些,再将gap变小,再进行插入排序。重复操作,直到gap为1;
额外知识:gap的取值,我们可以发现希尔排序的效率实际上就是取决于gap的取值,希尔排序最初是,arr.length 不断的除以2,进行划分,但是后来人们在不断的尝试中发现了效率更高的序列,我们以Knuth序列为例,在后续我们就加以实现,还有其他各式各样的序列,大家请自行尝试。
图示:
我们简单的用代码实现一下
public static int[] shellSort(int[] arr){
if(arr.length < 2 || arr == null){
return arr;
}
//使用kunth序列进行间隔划分
int h = 1;
while(h < arr.length/3){
h = 3*h +1;
}
for(int gap = h ; gap > 0 ; gap = (gap-1)/3){
shell(arr,gap);
}
return arr;
}
public static int[] shell(int[] arr,int gap){
for(int i = gap;i < arr.length;i+= gap){ //由于每一个被分割的序列的第一个数字不需要排序,所以从0 + gap下标下开始排序
int temp = arr[i]; //进行直接插入排序
int j;
for( j = i ; j > gap-1 && arr[j-gap] < temp;j -= gap){
arr[j] = arr[j-gap];
}
if(i != j ){
arr[j] = temp;
}
}
return arr;
}
}
时间复杂度:O(n^1.3) - O(n^1.5)
希尔排序的时间复杂度至今没有一个极其精确的值,但是书上给出的数据平均时间复杂度就是 O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定