数据结构与算法-希尔排序

背景铺垫

      希尔排序是D.L.Shell于1959年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是O(n2)的,希尔排序是突破这个时间复杂度的第一批算法之一。
      直接插入算法,应该说,它的效率在某些时候是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序,此时直接插入很高效。还有就是记录数比较少时,直接插入的优势也比较明显。可问题在于,这两个条件本身就过于苛刻,现实中记录少或者基本有序都属于特殊情况。

基本原理

将原本有大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录数个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序(所谓基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间)时,再对全体记录进行一次直接插入排序。分割时,为了是整个序列向基本有序发展,我们采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序的。

java代码实现

//声明待排序数组
int[] a = {9,1,5,8,3,7,4,6,2};
int i,j;
int flag;
int increment = a.length;
do { /* 通过递减increment,总序列逐步向基本有序迈进 */
     increment = increment/3+1; /* 增量序列(为什么这么取,仍然是数学难题) */
     for (i = increment; i < a.length; i++) {/* 这个循环功能就是对各子序列分别进行直接插入排序 */
	  flag = a[i];	/* 设置哨兵(当前记录) */
	  if (a[i] < a[i-increment]) { /* 当前关键字小于前一个关键字,需要将当前记录往前插入 */
              for (j = i-increment; j >= 0 && a[j] > flag; j-=increment) { /* 循环遍历当前关键字之前的所有关键字,并与哨兵比较,若大于哨兵,则记录后移 */
		   a[j+increment] = a[j];	/* 记录后移 */
	       }
	       //伪代码 a[j] = flag; 因为j循环最后执行j-increment,故这边j需+increment
	       a[j+increment] = flag;	/* 将哨兵插入正确位置 */
	   }
      }	
} while (increment > 1);

图示代码执行过程

分析:通过这一轮的排序,我们的数字1、2等小数字已经在前两位,而8、9等大数字已经在后两位,也就是说,通过这样的排序,我们已经让整个序列基本有序了。这其实就是希尔排序的精华所在,它将关键字较小的记录,不是一步一步地往前挪动,而是跳跃式地往前移,从而使得每次完成一轮循环后,整个序列就朝着有序坚实地迈进一步。


时间复杂度分析

  • 希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。
  • 这里的“增量”的选取就非常关键了。可究竟应该选取什么样的增量才是最好,目前还是一个数学难题,迄今为止还没有人找到一种最好的增量序列。不过大量的研究表明,当增量序列为dlta[k]=2t-k+1-1(0≤k≤t≤log2(n+1))时,可以获得不错的效率,其时间复杂度为O(n3/2),要好于直接排序的O(n2)。
  • 注意,增量序列的最后一个增量值必须等于1才行。另外由于记录是跳跃式地移动,希尔排序并不是一种稳定的排序算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值