浅谈数据结构-插入排序(直接插入、希尔排序)

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

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

一、直接插入法

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),所以比插入排序效果还是好的。

原文地址:https://www.cnblogs.com/polly333/p/4816240.html