希尔排序是 基本插入排序 的升级优化版。一个叫希尔的人于1959年提出的一种排序算法。本值也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
大部分场景中,增量是可以提前定义好的,也可以动态生成。一般使用数组的长度除以2作为首次间隔增量,然后在除以2,直到间隔缩小的为1位置。
核心:确定增量划分序列分组,对不同分组的数据进行插入排序。实际上每次都是在拿当前位置与间隔增量的距离中进行比较
动图可以很好的看出希尔排序的过程
希尔排序的分组交换过程
1 分组间隔为5,数组被划分为五组数据–[9,4],[1,8] ,[2,6],[5,3],[7,5] 作为待排序序列
2 对五组子序列进行插入排序
-
从当前分组开始位置即i=grep=5的位置开始,后续每一个元素都与对应间隔为grep=5的元素进行比较,10个数据,则需要扫描5轮
-
如果当前的元素 小于分组里已经排好的序的元素(分组的最后一个),
比如4 的元素小于9则需要交换 -
继续扫描分组里剩下元素如果是逆序,则进行交换,直到分组里元素都排好。
3 缩小增量 grep/2=5/2=2; 此时数据被分成2组每组5个----[4,2,5,8,5],[1,3,9,6,7]
重复步骤2,继续对此分组的2个子序列进行插入排序
4 缩小 增量为grep/2=2/2=1,此时数据被分成一组,存在10个数据,对其进行插入排序
5 缩小增量为0则停止循环,排序结束
时间复杂度
//希尔交换排序
public static void shellSwapSort(int[] arr){
long start=System.currentTimeMillis();
int len= arr.length;
for (int grep = len/2; grep>0; grep/=2) {//分组次数
for (int i = grep;i<len;i++) {//分组位置的每一个元素都需要与间隔k位置进行比较,比如10个数若分成5组则每组为2个元素则arr[5]比较arr[0],arr[6]比较arr[1]
if ( arr[i]<arr[i-grep]){//如果当前的元素 小于分组里已经排好的序的元素(分组的最后一个),例如:arr[5]<arr[0];arr[6]<arr[1]
int temp=arr[i];//当前需要处理的元素
//参考插入排序,当前元素与分组内所有元素进行比较
for (int j = i - grep; j >= 0&&temp<arr[j]; j -= grep) {//对每组内元素进行判断与交换,在分组内每遇到比当前元素大的都进行交换
arr[j] = arr[j] ^ arr[j + grep];
arr[j + grep] = arr[j] ^ arr[j + grep];
arr[j] = arr[j] ^ arr[j + grep];
}
}
}
}
long end= System.currentTimeMillis();
System.out.println(Arrays.toString(arr));
System.out.println("交换排序消耗时间:"+(end-start));
}
优点:
- 空间复杂度较好,O(1);作为改进版的插入排序,是一种相对高效的基于交换元素的排序方法。
缺点:
- 不稳定,在交换的过程中,会改变元素的相对次序。
- 希尔排序的时间复杂度依赖于增量序列函数,所以分析起来比较困难,当n在某个特定范围的时候,希尔排序的时间复杂都约为O(n1.3)
改进:在扫描分组数据使不必要每次都交换,找到最终的位置直接插入待处理元素即可
和插入排序基本相似,只不过此时的间隔为grep.插入排序的间隔是1罢了。
//希尔插入排序
public static void shellInsertSort(int[] arr){
int len = arr.length;
long start=System.currentTimeMillis();
for (int grep= len/2; grep >0 ; grep/=2) {
for (int j = grep; j <len; j++) {
if (arr[j]< arr[j-grep]) {//当前元素比分组的最后一个元素小
int temp = arr[j];//当前需要处理(插入)的元素
int k;
for (k=j-grep; k>=0&&temp<arr[k]; k-=grep) {//扫描分组数据在分组中找插入位置
arr[k+grep] = arr[k];
}
arr[k+grep] = temp;//插入元素
}
}
}
long end= System.currentTimeMillis();
System.out.println(Arrays.toString(arr));
System.out.println("希尔插入排序消耗时间:"+(end-start));
}
同样8000个数据,效率还是不错的,比插入排序要高许多