排序算法之希尔排序

算法分析:

希尔排序(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值