算法分析:
希尔排序(Shell Sort)是插入排序的一种,其实质就是分组插入排序,该方法又称缩小增量排序,因D.L.Shell于1959年提出而得名。它是对直接插入排序的一种改进,通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使得数据项大跨度的移动。
基本思想:
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
假设当前增量h为4(间隔),对[9,-16,21,23,-30,-49,21,30,30]进行shell排序:
①[9,-16,21,23,-30,-49,21,30,30]—>[-30,-16,21,23,9,-49,21,30,30]
②[-30,-16,21,23,9,-49,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
③[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
④[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
⑤[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
⑥[-30,-49,21,23,9,-16,21,30,30]—>[-30,-49,21,23,9,-16,21,30,30]
第一趟排序后只需要保证间隔为4的元素之间有序即可。从上面我们发现⑤⑥两步存在这样一个关系,第⑤的结果有可能造成之前判断过的元素再次无序(如果发生元素移动,没有发生的话将不会造成),需要进行第⑥的判断,所以我们在移动元素的时候应该使用一个循环来进行移动。
下一趟的判断时需要对增量进行递减处理;
从上面的分析可以看出,shell排序的关键在于增量序列的值,常用的增量序列由Knuth提出,该序列从1开始,由如下公式产生:h = h*3 + 1
程序中要反向计算增量序列:h = (h-1)/3
java代码实现:
package fanzhenhua.sort;
import java.util.Arrays;
public class ShellSort {
public static void shellSort(DataWrap[] data){
//希尔排序:根据增量进行直接插入排序,依次递减增量,直到增量为<1为止
int length=data.length;
//保存增量的基值
int h=1;
//这里的目的是求出增量的最大值
while(h<length/3){
h=h*3+1;
}
DataWrap temp=null;
while(h>0){
System.out.println("h======= "+h);
//从增量的起始位置开始判断起
for(int i=h;i<length;i++){
//保存待判断的元素
temp=data[i];
//如果待判断的元素小于增量之前的元素,则说明该增量所在的元素必须移动
if(data[i].compareTo(data[i-h])<0){
//获得比增量所在元素大的元素的位置
int j=i-h;
//循环移动,因为可能出现移动后的元素导致之前的增量间隔元素之间变成无序
//当然,如果没有存在移动现象的话,那么重复判断的时候不会对结果造成影响
for(;j>=0&&data[j].compareTo(temp)>0;j-=h){
//j+h为待判断元素的位置,该位置被较大元素覆盖。
data[j+h]=data[j];
}
//移动结束后,存储待判断的元素,位置为j+h(之所以需要+h的原因是因为上一个循环的结尾已经-h了)
data[j+h]=temp;
}
System.out.println(Arrays.toString(data));
}
//依次减小增量的值
h=(h-1)/3;
}
}
public static void main(String[] args) {
DataWrap[] data = {
new DataWrap(9, "")
,new DataWrap(-16, "")
,new DataWrap(21, "")
,new DataWrap(23, "*")
,new DataWrap(-30, "")
,new DataWrap(-49, "")
,new DataWrap(21, "")
,new DataWrap(30, "")
,new DataWrap(3, "")
,new DataWrap(67, "")
,new DataWrap(35, "")
,new DataWrap(5, "")
,new DataWrap(7, "")
,new DataWrap(15, "")
,new DataWrap(35, "")
};
System.out.println("排序之前:" + Arrays.toString(data));
shellSort(data);
System.out.println("排序之后:" + Arrays.toString(data));
}
}
运行结果:
排序之前:[25, -16, 15, 23, -30, -49, 21*, 30, 3, 67, 35, 5, 7, 21, 49]
h======= 13
[21, -16, 15, 23, -30, -49, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
[21, -16, 15, 23, -30, -49, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
h======= 4
[-30, -16, 15, 23, 21, -49, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 21, -16, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 21, -16, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 21, -16, 21*, 30, 3, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 3, -16, 21*, 30, 21, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 3, -16, 21*, 30, 21, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 23, 3, -16, 21*, 30, 21, 67, 35, 5, 7, 25, 49]
[-30, -49, 15, 5, 3, -16, 21*, 23, 21, 67, 35, 30, 7, 25, 49]
[-30, -49, 15, 5, 3, -16, 21*, 23, 7, 67, 35, 30, 21, 25, 49]
[-30, -49, 15, 5, 3, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-30, -49, 15, 5, 3, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
h======= 1
[-49, -30, 15, 5, 3, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, 15, 5, 3, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, 5, 15, 3, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, 3, 5, 15, -16, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 15, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 15, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 15, 21*, 23, 7, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 23, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 23, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 23, 25, 35, 30, 21, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 23, 25, 30, 35, 21, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 21, 23, 25, 30, 35, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 21, 23, 25, 30, 35, 67, 49]
[-49, -30, -16, 3, 5, 7, 15, 21*, 21, 23, 25, 30, 35, 49, 67]
排序之后:[-49, -30, -16, 3, 5, 7, 15, 21*, 21, 23, 25, 30, 35, 49, 67]
希尔排序算法分析:
1.增量序列的选择
Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
① 最后一个增量必须为1;
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。
2.Shell排序的时间性能优于直接插入排序
希尔排序的时间性能优于直接插入排序的原因:
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。
3.稳定性
希尔排序是不稳定的。参见上述实例,该例中两个相等的关键字21在排序前后的相对次序发生了变化。
参考文章:
https://blog.youkuaiyun.com/bruce_6/article/details/39120409