排序:希尔排序(算法)

简介: (注:如果想更好的理解希尔排序,请先看看我的上一篇博客插入排序,希望会对你有帮助。)

一、简介


希尔排序(Shell Sort)插入排序的一种算法,是对直接插入排序的一个优化,也称缩小增量排序

希尔排序是非稳定排序算法。

希尔排序因DL.Shell于1959年提出而得名。

注:为方便记忆算法,我习惯将其记作“三层for循环+if” ------** for(for(for(if)))**)

</br>

希尔排序是基于直接插入排序的以下两点性质而提出的改进方法:

1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

2.插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。


二、原理


希尔排序是将待排序的数组元素 按下标的一定增量分组 ,分成多个子序列,然后对各个子序列进行直接插入排序算法排序;然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序,排序结束。

**增量d 的范围: **1<= d < 待排序数组的长度  (d 需为 int 值)

**增量的取值: **一般的初次取序列(数组)的一半为增量,以后每次减半,直到增量为1。

第一个增量=数组的长度/2,

第二个增量= 第一个增量/2,

第三个增量=第二个增量/2,

以此类推,最后一个增量=1。

好的增量序列的共同特征:

① 最后一个增量必须为1;

② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。


三、原理过程图解


image.png

四、时间复杂度


**希尔排序的执行时间依赖于增量序列。    **

希尔排序耗时的操作有:比较 + 后移赋值。

时间复杂度情况如下:(n指待排序序列长度)

1) 最好情况:序列是正序排列,在这种情况下,需要进行的比较操作需(n-1)次。后移赋值操作为0次。即O(n)

2) 最坏情况:O(nlog2n)。

3) 渐进时间复杂度(平均时间复杂度):O(nlog2n)

希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(n²)好一些。

希尔算法的性能与所选取的增量(分组长度)序列有很大关系。只对特定的待排序记录序列,可以准确地估算比较次数和移动次数。想要弄清比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。

希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。希尔排序没有快速排序算法快,因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。

注:专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法。)


五、希尔排序的时间性能优于直接插入排序


当文件初态基本有序时,直接插入排序所需的比较和移动次数均较少。

当n值较小时,n和  n² 的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度O(n²)差别不大。(n指待排序序列长度)

在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量d 逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按d-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。

因此,希尔排序在效率上较直接插入排序有较大的改进。


六、性能分析(稳定性)


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


七、示例代码(Java)


1.核心代码(升序排列为例)

注:为方便记忆算法,我习惯将其记作“三层for循环+if” ------ for(for(for(if)))


public static int[]  insertionSort(int[] arr){
    if(arr == null || arr.length <= 1){
        return arr;
    }
    //希尔排序  升序
    for (int d = arr.length / 2;d>0;d /= 2){ //d:增量  7   3   1
        for (int i = d; i < arr.length; i++){ 
            //i:代表即将插入的元素角标,作为每一组比较数据的最后一个元素角标 
            //j:代表与i同一组的数组元素角标
            for (int j = i-d; j>=0; j-=d){ //在此处-d 为了避免下面数组角标越界
                if (arr[j] > arr[j + d]) {// j+d 代表即将插入的元素所在的角标
                    //符合条件,插入元素(交换位置)
                    int temp = arr[j];
                    arr[j] = arr[j + d];
                    arr[j + d] = temp;
                }
            }
        } 
    }
    return arr;
}

2.希尔排序详细示例代码

import java.util.*;
public class ShellSortDemo {
   public static void main(String[] args) {
        int[] arr = {99, 5, 69, 33, 56, 13, 22, 55, 77, 48, 12, 88, 2,69,99};
        System.out.println("排序之前数组:");
        printArray(arr);
        //希尔排序
        insertionSort(arr);
        System.out.println("希尔排序后数组:");
        System.out.println(Arrays.toString(arr));
    }
    private static int[]  insertionSort(int[] arr){
        if(arr == null || arr.length <= 1){
            return arr;
        }
        //希尔排序  升序
        for (int d = arr.length / 2;d>0;d /= 2){ //d:增量  7   3   1
            System.out.println("增量取值:" + d);
            for (int i = d; i < arr.length; i++){ 
                //i:代表即将插入的元素角标,作为每一组比较数据的最后一个元素角标 
                //j:代表与i同一组的数组元素角标
                for (int j = i-d; j>=0; j-=d){ //在此处-d 为了避免下面数组角标越界
//                    System.out.println("i:" + i + "  j:" + j +"   j+d="+(j+d));
                    if (arr[j] > arr[j + d]) {// j+d 代表即将插入的元素所在的角标
                        //符合条件,插入元素(交换位置)
                        swap(arr,j,j+d);
                    }
                }
            }
            /*测试:此段代码只为查看希尔排序的每次增量变化过程,正常写程序时不要添加星号注释的这部分代码*/
            for (int m = 0; m < arr.length; m++) {
                System.out.print(arr[m] + " ");
            }
            System.out.println("");
            /**/
        }
        return arr;
    }
    /*
   发现无论什么排序。都需要对满足条件的元素进行位置置换。
   所以可以把这部分相同的代码提取出来,单独封装成一个函数。
   */
    public static void swap(int[] arr,int a,int b)
    {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    /*
    * 最近才知道打印数组不用自己写方法这么麻烦,java API中就有 Arrays.toString(arr);
    * 越简单的越容易忽略,这是最后一次自己写这个方法,以后就用自带的方法
    */
    public static void printArray(int[] arr)
    {
        System.out.print("[");
        for(int x=0; x<arr.length; x++)
        {
            if(x!=arr.length-1){
                System.out.print(arr[x]+", ");
            }else{
                System.out.println(arr[x]+"]");
            }
        }
    }
}

运行结果:(原理过程可自行测试,运行出过程,得到结果)

image.png

版权声明:本文为博主原创文章,转载请必须注明出处,谢谢!

目录
相关文章
|
3月前
|
算法
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
|
16天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
63 8
|
16天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
54 7
|
1月前
|
搜索推荐 Shell
解析排序算法:十大排序方法的工作原理与性能比较
解析排序算法:十大排序方法的工作原理与性能比较
52 9
|
1月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
23 1
|
1月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
24 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
32 0
|
1月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
72 0
|
3月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
42 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
3月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
42 0
下一篇
无影云桌面