zoukankan      html  css  js  c++  java
  • 浅谈数据结构-插入排序(直接插入、希尔排序)

    插入排序:数组中获取数据,遍历数组中数据进行比较,找到合适位置,进行插入工作。

    直接插入和希尔排序关键区别在于:希尔排序是有分组,然后进行迭代,组间插入数据,是一种变形的插入排序算法。

    一、直接插入法

    1、算法思想

    扑克牌帮你占卜星座情人

    上图是一张扑克牌,在摸牌阶段就是直接插入操作。

    (1) 数组中下标为1 的元素视为元素个数为 1 的有序序列。(元素0表示哨兵)

    (2) 然后,我们要依次把 R1, R2, ... , RN-1 插入到这个有序序列中。所以,我们需要一个外部循环,从下标 1 扫描到 N-1 。

    (3) 接下来描述插入过程。假设这是要将 Ri 插入到前面有序的序列中。由前面所述,我们可知,插入Ri时,前 i-1 个数肯定已经是有序了。

    2、算法代码

    //插入排序
    void SortAlgorithm::InsertSort(pSqlList pList)
    {
        //插入排序,有哨兵概念
        int i,j;
        printf("开始验证插入排序");
        for (i =1;i<pList->length-1;i++)
        {
            //遍历获取数组中元素,如果后续元素比较小,进行插入工作,插到这个元素左边,如果大,继续循环
            if (pList->SqlArray[i+1] < pList->SqlArray[i])
            {            
                //在数组中0位置为哨兵作用,获取此次较小元素
                pList->SqlArray[0] = pList->SqlArray[i+1];
                //再将较小元素,和之前元素比较,如果发现较大的,停止,然后插入
                for (j = i;j>0 && pList->SqlArray[0] < pList->SqlArray[j];j--)
                {
                    //向后移动数据,让其有空间插入工作,第一个是插到i+1位置,就是较小元素的位置
                    pList->SqlArray[j+1] = pList->SqlArray[j];
                }            
                //插入工作,因为j--,此处是j+1
                pList->SqlArray[j+1] = pList->SqlArray[0];
            }
            PrintSqlList(pList);
        }
    
    }

    3、代码分析

    image

    上图就是程序运行结果:首相数组是{0,50,10,90,30,70,40,80,20}

    1、先开始找无序的数,发现50>10,那么选取10作为基准值,开始进行插入操作。

    2、从10坐标往前遍历,要将10的插入正确位置。向前遍历(比10大,继续向前寻找),当发现比10小,停止,进行插入工作。

    3、插入工作将是向后移动数据,此时50和10交换位置。第一循环停止。

    4、比较50与90,是有序的,下一条记录。

    5、比较90和30,发现30<90进入插入工作,将30插入到{10,50,90}有序序列中,输出的就是{30,10,30,50,90,70,40,80,20}

    6、比较90和70,同样道理,最后输出争取结果。关键在于向有序数组中,判断插入到正确位置。

    代码实现上有冒泡算法的影子,就是前后元素比较,此处不是交换,而是向前的有序数组进行插入工作。

    二、希尔排序

    希尔排序是一种变异的插入算法,在引入二叉树的概念后(分组,每次遍历组的大小变化,同时是夸组插入,将可能比较大的元素交叉插入到后面组,较小的插入前面组),遵循先粗分再细分的原则,实现交叉插入。

    1、算法思想

    插入算法基于分组交叉插入,首先第一步是分组,确定分组大小。然后在组间进行插入工作,所以插入时的步长为分组大小。

     

    上图来源于网络,很少说明了希尔排序算法的精髓,交叉插入工作。

    第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。

    接下来,按照直接插入排序的方法对每个组进行排序。

    第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。

    按照直接插入排序的方法对每个组进行排序。

    第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。

    按照直接插入排序的方法对每个组进行排序。此时,排序已经结束

    需要注意一下的是,图中有两个相等数值的元素 55 。我们可以清楚的看到,在排序过程中,两个元素位置交换了。

    2、希尔排序的步长

    image

    已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),该序列的项来自

    这两个算式。

    当增量序列为dlta[k]=2t-k+1 (0<=k<=t<=|log(n+1)|)时,可以获得不错的效果。其时间复杂度为O(n3/2),要好于直接排序。

    这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

    3、程序代码

    //希尔排序
    void SortAlgorithm::ShellSort(pSqlList pList)
    {
        //希尔排序是插入排序的改进,先分组,再插入,而且插入是夸组插入。先有分组,在执行插入
        int i,j;
        printf("开始验证希尔排序");
        int increment = pList->length;
        do 
        {
            //小于3,时,保证能循环
            increment = increment/3 +1;    //增量寻列
            //执行夸组插入工作,跨度大小为increment,所以j的跨度就是increment
            for (i = increment +1;i<pList->length;i++)
            {
                //从组的后部分和前部分比较,如果后面小,进行夸组插入工作
                if (pList->SqlArray[i] < pList->SqlArray[i-increment])
                {
                    //和插入排序很像
                    pList->SqlArray[0] = pList->SqlArray[i];                
                    for( j = i-increment;j>0&&pList->SqlArray[0]<pList->SqlArray[j];j = j-increment)
                    {
                        //比较大移动后面
                        pList->SqlArray[j+increment] = pList->SqlArray[j];
                    }
                    pList->SqlArray[j+increment] = pList->SqlArray[0];
                }
            }
    
            PrintSqlList(pList);
        } while (increment>1);    
    }

    4、代码分析

    上述代码中while跳出条件是步长大于1,同时在进行插入工作是,是j=j-increment,这样才实现交叉插入,向右移动步长也是j+increment

    image

    在程序中输入的数组为:{0,50,10,90,30,70,40,80,60,20}。步长为4,所以应该是坐标为1的和坐标为5的进行交叉比较,插入。

    首先步长为4,50和70比较,50<70,不变,i++。然后10与40,后来到90和80交换,30和60不交换,最后是70和20交换,这是发现位置1为50,50和20,继续交换,最后结果就是{20,20,20,80,30,50,40,90,60,70}。

    第二次,此时incerment = incerment/3+1,此时incerment为2,说明步长为2,意味坐标1和坐标3之间交叉比较,进行插入。当然往后1,3,5也是需要判断的。

    第三次继续,因为increment =2,继续循环,此时incerment是1,意味两两交叉交换插入了,但此时序列是基本有序了,之间数据交换很小了。

    三、算法分析

    1、直接插入排序复杂度

    从空间上看,只需要 一个记录的辅助空间,空间的复杂度为O(1),当最好的情况下,也就是数组是有序表,没有移动记录时间复杂度为O(n)。

    最坏的情况是,排序表是逆序,此时需要比较1+3+4+5+6….n-1 = (n+2)(n-1)/2,时间复杂度为O(n2).同样的O(n2)时间复杂度,直接插入排序法比冒泡排序和简单选择排序的性能要好一些。

    所以,数据越接近正序,直接插入排序的算法性能越好

    2、希尔排序复杂度分析

    希尔排序中步长选择很重要,Donald Shell 最初建议步长选择为N/2并且对步长取半直到步长达到1。虽然这样取可以比O(N2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。

    研究证明希尔排序最好情况是O(nlogn),最坏的情况是O(n*n0.5),所以比插入排序效果还是好的。

  • 相关阅读:
    记录移动端原生开发基础样式
    windows关闭打开应用的时候的安全弹窗
    thinkPad触控板手势安装
    git修改账号密码
    ssh免密登录失效处理
    xxl-rpc remoting error(Connection refused (Connection refused)), for url : http://xxxxxx:19997/run
    utools好用插件分享
    Android Studio打包apk闪退处理
    maven下载jar包失败处理
    微信文件下载位置
  • 原文地址:https://www.cnblogs.com/polly333/p/4816240.html
Copyright © 2011-2022 走看看