首先看一下之前使用简单插入排序存在的问题:
当存在一个数组,其最后一个数据为最小值,如:arr={5,6,16,34,33,2}
这样的话需要循环到最后一次,也就是第6次的时候,才可以将2排到前边,效率非常低
但是希尔排序可以很好的解决这个问题,其是简单插入排序改进后的一个效率较高的排序方式
,也叫缩小增量排序
复杂度:
平均时间: O(nlogn)
最差: O(n²)
最优: O(n^1/3)
空间复杂度:O(1)
稳定性: 不稳定
比插入和选择排序快得多,且数组越大,优势越大
一、基本思想
希尔排序按某个增量分组,对每个分组使用直接插入排序实现,随着增量逐渐减小,每组数据逐渐增加,当增量减少至1时,仅剩下完整的一组,排序结束。
二、排序分析
图片引用自https://blog.csdn.net/qq_28081081/article/details/80598960
三、代码实现
/**
* 希尔排序
* @author MaoLin Wang
* @date 2019/10/2817:43
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr={214,32,11,2,3,2,66,33,54,12};
shellSort(arr);
}
public static void shellSort(int[]arr){
int temp=0;
int count=0;
for (int group =arr.length/2;group>0;group/=2){
for (int i=group;i<arr.length;i++){
for (int j=i-group;j>=0;j-=group){
if (arr[j]>arr[j+group]){
temp=arr[j];
arr[j]=arr[j+group];
arr[j+group]=temp;
}
}
}
System.out.println("第"+(++count)+"次排序结果:");
System.out.println(Arrays.toString(arr));
}
}
}
结果:
第1次排序结果:
[2, 32, 11, 2, 3, 214, 66, 33, 54, 12]
第2次排序结果:
[2, 2, 3, 12, 11, 32, 54, 33, 66, 214]
第3次排序结果:
[2, 2, 3, 11, 12, 32, 33, 54, 66, 214]
测试100000条数据耗时:
int[] arr =new int[100000];
for (int i=0;i<100000;i++){
arr[i]=(int)(Math.random()*100000);
}
long begintime=System.currentTimeMillis();
System.out.println("开始时间"+begintime);
shellSort(arr);
long endtime=System.currentTimeMillis();
System.out.println("结束时间"+endtime);
System.out.println("用时:"+(endtime-begintime)+"ms");
结果:
开始时间1572260920132
结束时间1572260930389
用时:10257ms
发现用了10000多ms,不但没有提高效率,反而低了非常多,这是为什么呢?
仔细看我们的代码进行交换的条件,发现只要满足arr[j]>arr[j+group]就要进行交换,无疑增加了系统开销
接下来对代码进行优化:
public static void shellSort2(int arr[]) {
for (int group = arr.length / 2; group > 0; group /= 2) {
for (int i = group; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - group]) {
while ((j - group) >= 0 && temp < arr[j - group]) {
/**
* 开始移动,将比arr[j]大的arr[j-group]、arr[j-group-group]......按顺序移动到后一个增量的位置
* 如 3, 2, 11, 21, 66, 32, 214, 4
* 在group减少到2,比较32和4的时候
* 1. 4<32 且满足while条件, 所以将32移动到4,temp=4,j=5,指向32的位置
* 此时的数据为:3, 2, 11, 21, 66, 32, 214, 32
* 2.继续while循环
* j-group=3>0 temp=4<arr[3]=21,满足while条件
* 执行arr[j]=arr[j-group] -> 将21的位置移动到32,temp仍然为一开始的4,j=3,指向21的位置
* 此时数据为:3, 2, 11, 21, 66, 21, 214, 32
* 3.继续循环while
* j-group=1>0 temp=4>arr[1]=2 不满足while条件,退出循环
* 4.此时j=3,将temp=4赋值给arr[j]
* 此时数据为:3, 2, 11, 4, 66, 21, 214, 32
*/
arr[j] = arr[j - group];
j -= group;
}
//结束while循环后,将temp插入到arr[j]
arr[j] = temp;
}
}
}
}
或用for循环:
public static void shellSort(int[] arr){
int j;
for (int gap = arr.length/2; gap >0 ; gap/=2) {
for (int i = gap; i < arr.length; i++) {
int temp=arr[i];
for ( j = i; j -gap >=0&&temp<arr[j-gap] ; j-=gap) {
arr[j]=arr[j-gap];
}
arr[j]=temp;
}
}
}
同样测试10000条数据排序的耗时:
开始时间1572264172662
结束时间1572264172692
用时:30ms
耗时从1w多ms减小到30ms,多次测试上下波动也不会超过20ms