算法入门---java语言实现的希尔排序小结

来源:互联网 发布:手机号码扫号软件 编辑:程序博客网 时间:2024/05/17 23:20
标准实现:
  1. /**
  2. * 希尔排序,是一种改进的插入排序。它是基于插入排序在元素基本有序的情况下效率很高(会中断比较直接返回)这一特性。
  3. * 核心思想:以不同的间隔来分割待排序的序列,间隔按照某种规律减小,直到1,间隔的选择一定程度上影响希尔排序的
  4. * 效率。被分割成不同的子序列后,依次对每个子序列进行插入排序,然后用更小的间隔分割序列再进行排序。
  5. * 直到间隔为1,也就是我们的插入排序,此时元素基本就已经有序。整体看来效率会比一般插入排序高。
  6. * 以几为间隔就相当于把这个序列分成了几个子序列。如: 1 4 6 7 8 3 4.以2 为间隔就是 (1 6 8 4)、
  7. * (4 7 3)这样。就是这个数以差距2跳着找亲戚,然后下个数也是以差距2跳着找,但是再下一个数的时候,你想想此时
  8. * 就和第一个数重复了,它其实是第一个数哪一组的!!
  9. *
  10. * @param src 数据源。
  11. */
  12. public static void standardShellSort(int[] src){
  13. int len = src.length;
  14. //gap是间隔,此时初始化就已数组长度的一般
  15. int gap;
  16. //间隔按照2倍减少直到为1.
  17. for(gap = len/2 ;gap > 0 ;gap /= 2){
  18. //i到gap为一组数的第一个元素的集合,其它的数就是这个数加上gap。
  19. //所以要大于0小于gap.来标识每个组的第一个元素。
  20. for(int i=0;i<gap;i++){
  21. //每个组的元素,如:i = 0的时候直接就是找到了整个第一组元素,然后进行插入排序
  22. 此处是每一个分组里面的所有的元素
  23. for(int j = i+gap;j < len ;j += gap){
  24. //如果发现比前面的数小,此处不仅仅交换一下就行了,应该一直比较到最前面(类似冒泡)
  25. if(src[j] < src[j-gap]){
  26. int k = j-gap;
  27. int temp = src[j];
  28. //当前面还有元素,并且当前元素比前面的元素小的时候,需要往前移动。
  29. //此时用的就是改进版的插入,不是交换,而是直接赋值
  30. while(k >= 0 && temp < src[k]){
  31. //不用担心覆盖前面的,因为已经拿出来了temp;
  32. src[k+gap] = src[k];
  33. //循环执行。
  34. k -= gap;
  35. }
  36. //最后把拿出来的那个元素放在找到的位置。符合要求执行完后k就会又见一次gap,
  37. //显然最后出来的时候应该k+gap为合适的值。
  38. //其实理解的不深。
  39. src[k+gap] = temp;
  40. }
  41. }
  42. }
  43. }
  44. }

改进思想的实现:
  1. /**
  2. * 这是另一种拆分思路的,前面的拆分思路是以每个组的首元素为起点,然后每次加gap找到所有的元素
  3. * 进行插入排序,执行完第一组在执行第二组。
  4. * 而此处那?思想是循环整个数组在这个数组是++的,就是索引一个个增加。只不过按照对应的gap进行处理。
  5. * 找到这个索引后,和它的(索引-gap)个元素一次比较,每次都是调gap的间隔,直到比较到合适的位置。
  6. * 这样其实还是各自在处理自 己组的数,只不过是由于i++轮流处理了。你处理一次,我处理一次。而且还是
  7. * 标准的插入,往前面比较的。
  8. */
  9. public static void sort(int[] src) {
  10. int len = src.length;
  11. for (int gap = len/2; gap > 0; gap /= 2) {
  12. for (int i = gap; i < len; i++) {
  13. //此时注意j-gap >= 0.必须包含等于,很明显等于的时候j、j - gap两个也应该比较。
  14. //不然第一个数就没办法参与排序了
  15. for (int j = i; (j - gap) >= 0 && src[j] < src[j - gap]; j -= gap) {
  16. int temp = src[j];
  17. src[j] = src[j - gap];
  18. src[j - gap] = temp;
  19. }
  20. //gap = 5 排序前 7 1 4 0 0 2 0 4 2 2 此时就会分成5组如下:
  21. // 7 2
  22. // 1 0
  23. // 4 4
  24. // 0 2
  25. // 0 2
  26. //需要(j - gap) >= 0 && src[j] < src[j - gap];两重判断
  27. //第一组i = 5 符合,会排序。(2 7)
  28. //第二组i = 6 符合,会排序。(0 1)
  29. //第三组 i = 7 符合,但不符合第二个小于判断所以不会排序。(4 4)
  30. //此处也能证明是稳定的,但是考虑到各个组还会合并,所以最终是不稳定的。
  31. //第四组 i = 8 符合,但不符合第二个小于判断所以不会排序。(0 2)
  32. //第五组 i = 9 符合,但不符合第二个小于判断所以不会排序。(0 2)
  33. //排序完:2 0 4 0 0 7 1 4 2 2
  34. //gap = 2 排序前 2 0 4 0 0 7 1 4 2 2 此时会分成 2组.
  35. // 2 4 0 1 2
  36. // 0 0 7 4 2
  37. //需要进行如下两个j-gap>0,并且src[j] < src[j - gap]
  38. //此时可以注意假如第一个判断那是>0的时候,包含第一个数的那个组永远不会让它参与排序。
  39. //第一组 :
  40. //i = 2、 不符合,所以不会排序。( 2 4 0 1 2);
  41. //i = 4、 符合,进行排序。( 2 0 4 1 2)、并且它还满足继续排序(0 2 4 1 2);
  42. //i = 6、 符合,进行排序。( 0 2 1 4 2)、继续(0 1 2 4 2);
  43. //i = 8、 符合, 进行排序.( 0 1 2 2 4);
  44. //第二组 :
  45. //i = 3 不符合,由于相等不会排序。( 0 0 7 4 2);(组内稳定)
  46. //i = 5 不符合,不会排序。( 0 0 7 4 2);
  47. //i = 7 符合, 会排序。( 0 0 4 7 2);
  48. //i = 9 符合, 会排序。( 0 0 4 2 7)且继续排序(0 0 2 4 7);
  49. //排序完: 0 0 1 0 2 2 2 4 4 7 .注意上面过程时前面的索引,可以看出来他们是交替执行的,排序一下
  50. // 第一组,然后排序第二组,然后再排序第一组,如此往复。
  51. //gap = 1; 排序前0 0 1 0 2 2 2 4 4 7。 此时就一组了。 (插入排序就是步长为1,不是0)
  52. //需要进行如下两个j-gap>0,并且src[j] < src[j - gap]
  53. //i = 1、不符合,不会排序(0 0 1 0 2 2 2 4 4 7)
  54. //i = 2、不符合,不会排序(0 0 1 0 2 2 2 4 4 7)
  55. //i = 3、符合, 会排序(0 0 0 1 2 2 2 4 4 7)
  56. //i = 4、不符合,不会排序(0 0 0 1 2 2 2 4 4 7)
  57. //i = 5、不符合,不会排序(0 0 0 1 2 2 2 4 4 7)
  58. //i = 6、不符合,不会排序(0 0 0 1 2 2 2 4 4 7)
  59. //i = 7、不符合,不会排序(0 0 0 1 2 2 2 4 4 7)
  60. //i = 8、不符合,不会排序(0 0 0 1 2 2 2 4 4 7)
  61. //i = 9、不符合,不会排序 (0 0 0 1 2 2 2 4 4 7)
  62. //排完: 0 0 0 1 2 2 2 4 4 7, 最后一组也可以看出排序需要做的动作越来越少,这就是利用插入排序的特性
  63. //gap = 0;直接退出。由于一直是自己本身j-=gap,第一个不符合要求,没必要再进行排序。
  64. }
  65. }
  66. }
        这样实现代码量虽然少了,但是交换的次数变多了,也就是未优化的插入排序。接下来我们进一步对它做优化处理。
最终版:
  1. /**
  2. * 效率更高的希尔排序,使用的是优化后的原则排序模型
  3. * @param src 数据源
  4. */
  5. public static void betterShellSort(int[] src){
  6. int len = src.length;
  7. int gap;
  8. for(gap = len/2;gap > 0;gap /= 2){
  9. for(int i = gap;i < len;i++){
  10. //用来存放将要进行插入排序的元素。
  11. int temp = src[i];
  12. int j;
  13. //利用的是先把最初的取出来,然后其它比前面小的话,就把前面的移动到后一个位置。
  14. for(j = i;(j-gap >= 0)&&(temp < src[j-gap]);j-=gap){
  15. src[j] = src[j-gap];
  16. }
  17. //最终需要放入找到的合适的位置,两种情况:
  18. //1、到了最前面也就是j = gap的地方,放入即可
  19. //2、没到最前面,前面的元素都比它小了。有j-=gap,得到这个位置,然后这个位置>gap.
  20. // 但是没有前面的大了,那么就放入就好!所以就是放入j就行了。
  21. src[j] = temp;
  22. }
  23. }
  24. }
         核心思想就是把插入排序的优化方式加进去。改进后和以前相同的测试环境,平均下来真的比插入快很多。
测试数据:
  1. 10000
  2. name: 冒泡排序1 花费了 = 0ms
  3. name: 冒泡排序2 花费了 = 0ms
  4. name: 冒泡排序3 花费了 = 0ms
  5. name: 冒泡排序4 花费了 = 15ms
  6. name: 冒泡排序5 花费了 = 0ms
  7. 平均:3 (有时候甚至全都是0)
  8. 50000
  9. name: 冒泡排序1 花费了 = 15ms
  10. name: 冒泡排序2 花费了 = 0ms
  11. name: 冒泡排序3 花费了 = 0ms
  12. name: 冒泡排序4 花费了 = 15ms
  13. name: 冒泡排序5 花费了 = 0ms
  14. 平均 6
  15. 100000
  16. name: 冒泡排序1 花费了 = 32ms
  17. name: 冒泡排序2 花费了 = 15ms
  18. name: 冒泡排序3 花费了 = 16ms
  19. name: 冒泡排序4 花费了 = 15ms
  20. name: 冒泡排序5 花费了 = 16ms
  21. 平均:19
  22. 1000000
  23. name: 冒泡排序1 花费了 = 187ms
  24. name: 冒泡排序2 花费了 = 187ms
  25. name: 冒泡排序3 花费了 = 172ms
  26. name: 冒泡排序4 花费了 = 171ms
  27. name: 冒泡排序5 花费了 = 156ms
  28. 平均:174
  29. 5000000
  30. name: 冒泡排序1 花费了 = 1047ms
  31. name: 冒泡排序2 花费了 = 983ms
  32. name: 冒泡排序3 花费了 = 951ms
  33. name: 冒泡排序4 花费了 = 936ms
  34. name: 冒泡排序5 花费了 = 952ms
  35. 平均: 972
       由于比一般的排序块多了,导致前面相对较少的数字量根本看不出什么,所以额外多加了两组。

时间复杂度:
       希尔排序的时间复杂度与增量序列的选取有关。希尔排序时间复杂度的下界是n*log2n,适合于中等规模的排序, 一般来
说对于希尔排序,由于结合了插入排序的特性,所以它的时间复杂度一般都远小于n^2.

稳定性:不稳定。
       我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的
插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

关于增量(间隔)
       1、最后的曾量一定要是1。
       2、据听说用素数比较好。
       3、一般我就取数组的一般,每次都折一般。
0 0
原创粉丝点击