插入排序算法之直接插入排序和希尔排序

插入排序算法

有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。

直接插入排序

直接插入排序的排序思路是:每次将一个待排序的元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。

例子:

有序列:

开始时,有序序列只有一个元素就是第一个元素(红色),后面的无序序列(绿色)。接下来,取无序序列中的第一个元素3,把它放到有序系列的合适位置。方法是,从有序序列的最后面向前,依次和3比较,如果比3大,就向后移动一个位置,直到找到比3小的元素,然后把3插到后面(由于后面的元素已经依次移动,所以该位置已经空出),或者有序序列中没有比3小的元素,则将3放在有序序列的第一个位置(由于移动,该位置已经空出)。最后结果为:

同样,取无序队列中的第一个元素,也就是6,然后,从有序序列的后面依次向前比较,首先是8,大于6,则向后移动(注意,8后移则会占据6的位置,所以要提前将6存一份)。接着比较3和6,3比6小,所以将6插在3的后面(也就是原来8的位置,8已经后移,该位置已空)。所以结果就是:

继续下去,直到安排好最后一个元素。

代码:

代码也很简单,主要的就是比较和后移,但要注意,要将待排序的元素多存一份,因为后移时,会占据该元素的位置。

#include <stdio.h>

void insert_sort(int value[],int n)
{
    int i = 1;
    for(;i < n;i++)
    {
        if(value[i] < value[i - 1])
        {
            int j = i - 1;
            int temp = value[i];//value[i]的再移动的过程中会变化,所以要提前存储下来
            for(;j >= 0;j--)
            {
                if(temp < value[j])//当value[i]小于value[j]的时候,将value[j]向后移动
                {
                    value[j + 1] = value[j];
                    continue;
                }
                //否则退出循环
                break;
            }
            //退出循环时,说明value[i]大于value[j],这时,应该将value[i]放在value[j]的后面(后面一个位置已经移空)
            //还有一种情况是前面所有元素都比value[i]大
            value[j + 1] = temp;
        }
    }
}
int main()
{
    int value[] = {8,3,6,2,4,5,7,1,9,0};
    insert_sort(value,10);
    printf("排序后的结果为:\n");
    int i = 0;
    for(;i < 10;i++)
        printf("%d  ",value[i]);
    printf("\n");
    return 0;
}

时间复杂度

只是定性的一个分析:从代码中可以看出,算法的核心就是比较和移动,如果序列本身是有序的,那么只需要n次比较,不需要移动,所以此时的时间复杂度为O(n)。如果序列是倒序的,则排第n个元素时,需要与前n-1个元素进行比较,前n-1个元素也都要后移。这样n从1取到n就是,比较和移动的次数都是0+(2-1)+(3-1)+..+(n-1)结果就是n*(n-1)/2,所以是O(n2)级别。书上说,直接插入排序的平均时间复杂度也是O(n2)级别。

是否是稳定的

是的。(稍微想一下就知道)

希尔排序

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。

希尔排序算法的时间复杂度和步长的选取有关,平均时间复杂度为O(nlog2n),最坏为O(n2),最好为O(n).

直接插入排序更适合于原始记录基本有序的集合。这是因为如果记录基本有序,那么直接插入排序时移动的次数就会很少。而希尔排序正式利用了直接排序的这一个特点,希尔排序将数据按一定的步长进行分组,是的记录很快就会达到整体基本有序。

例子:

有序列:

首先选择一个步长,前面说过不同的初始步长会导致不同的时间复杂度,书上说,希尔排序的步长选择是一个数学难题,所以我们不要纠结。最常用的初始步长就是length/2。在这个例子中,length=9,所以初始步长step=4。然后我们将原序列分成四组(记住,步长是多少就分成多少组!!!!),分组的原则是,同一组中的元素中,每两个元素之间的下标的差为步长step。分组结果如下(相同颜色为一组)

然后,分别对每一组按照直接插入排序的方法进行排序(注意,此时每组中相邻的两个元素之间的下标差是步长step,而不是1)结果为:

然后改变步长:step=step/2,所以这一轮的步长为2,然后将数组分成两组(再次说明,步长是多少,就分多少组)。如下(相同颜色为一组):

然后按照直接插入进行排序

然后,继续改变步长,step=step/2,所以这一轮的步长为1,此时素组就分成一组了:

然后,按照直接插入排序进行排序,

接下来改变步长,step=step/2,步长为0,结束。

写代码:

通过上面的例子我们可以看到,实际上对分成的每一个组,进行的操作还是直接插入排序,只不过处理时,要考虑相邻两个元素之间的下标差不在是1,而是step。所以,我们首先要对上面直接插入排序的函数insert_sort()进行必要的修改,加入两个参数:首元素的下标(以确定是对哪一组数据进行直接排序)和步长。如下:

/**
 * 修改直接插入排序的函数
 * 加上了两个参数:start_index表示每组的第一个元素的下标
 * step表示步长
 * */
void insert_sort(int value[],int n,int start_index,int step)
{
    int i = start_index + step;
    for(;i < n;i+=step)
    {
        if(value[i] < value[i - step])
        {
            int j = i - step;
            int temp = value[i];//value[i]的再移动的过程中会变化,所以要提前存储下来
            for(;j >= 0;j-=step)
            {
                if(temp < value[j])//当value[i]小于value[j]的时候,将value[j]向后移动
                {
                    value[j + step] = value[j];
                    continue;
                }
                //否则退出循环
                break;
            }
            //退出循环时,说明value[i]大于value[j],这时,应该将value[i]放在value[j]的后面(后面一个位置已经移空)
            //还有一种情况是前面所有元素都比value[i]大
            value[j + step] = temp;
        }
    }
}

最后主函数中,主要任务就是分组,然后对每组数据都调用insert_sort()函数,还是再次强调:step是多少就分多少组!!!

完整代码

#include <stdio.h>

/**
 * 修改直接插入排序的函数
 * 加上了两个参数:start_index表示每组的第一个元素的下标
 * step表示步长
 * */
void insert_sort(int value[],int n,int start_index,int step)
{
    int i = start_index + step;
    for(;i < n;i+=step)
    {
        if(value[i] < value[i - step])
        {
            int j = i - step;
            int temp = value[i];//value[i]的再移动的过程中会变化,所以要提前存储下来
            for(;j >= 0;j-=step)
            {
                if(temp < value[j])//当value[i]小于value[j]的时候,将value[j]向后移动
                {
                    value[j + step] = value[j];
                    continue;
                }
                //否则退出循环
                break;
            }
            //退出循环时,说明value[i]大于value[j],这时,应该将value[i]放在value[j]的后面(后面一个位置已经移空)
            //还有一种情况是前面所有元素都比value[i]大
            value[j + step] = temp;
        }
    }
}
int main()
{
    int value[] = {7,3,6,2,5,1,8,9,4};

    int len = 9;
    int step = len;
    step /= 2;
    while(step != 0)
    {
        int j = 0;
        for(;j < step;j++)//将原数据集分成了step组数据
        {
            insert_sort(value,len,j,step);
        }
        step /= 2;

    }

    printf("排序后的结果为:\n");
    int i = 0;
    for(;i < len;i++)
        printf("%d  ",value[i]);
    printf("\n");
    return 0;
}

最后附上word文档和源文件的链接:

链接:http://pan.baidu.com/s/1c2zrh1e 密码:zy5a

时间: 2024-12-14 12:53:08

插入排序算法之直接插入排序和希尔排序的相关文章

算法学习之排序算法(四)(希尔排序)

1.算法思想 先将整个待排元素序列分割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序.因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高. 希尔(Shell)排序又称为缩小增量排序,它是一种插入排序.它是直接插入排序算法的一种威力加强版. 希尔排序的基本思想是 把记录按步长 gap 分组,对每组

排序---内部排序算法(快排、希尔排序、归并排序、基数排序、冒泡、选择排序)比较

1.内部排序的复杂度总结 1)时间复杂度 4种排序的平均时间复杂度是O(nlog2n),"快些以nlog2n的速度归队"(快排.希尔排序.归并.堆排序) 最坏情况下,快排的时间复杂度为O(n*n) 2)空间复杂度 O(log2n)快排 O(n)归并 O(rd)基数 其他都是O(1) 3)稳定性 不稳定的:"考研复习痛苦啊,情绪不稳定,快些选一堆好友来聊天吧"(快排.希尔.简单选择排序.堆排序) 其他都是稳定的. 4)一趟排序,保证一个关键字到达最终位置 交换类(起泡

插入排序与shell排序(希尔排序)

1 .插入排序的过程如同我们平时打扑克牌取牌插入的过程,不断将取出的扑克牌插入已经排好的地方. 插入排序过程初始有序区间大小为1,取出无序区间的首元素,查找有序区间的合适位置,进行插入.不断重复上述过程,即可完成操作. 图解示例 1 //插入排序 2 //karllen @2015 3 void insertSort() 4 { 5 int i ,j ,temp; 6 for(i = 1;i<n;++i) //从第二个元素开始插入 7 { 8 temp = a[i]; //a[i]会被覆盖,临时

[golang] 数据结构-希尔排序

除了上篇介绍的二分插入排序,还有这次介绍的希尔排序(Shell's Sort),也是对直接插入排序算法的优化. 原理希尔排序,就是按某个增量值对数据进行分组,每组单独排序好后,再缩小这个增量,然后按新增量对数据分组后每个分组再各自排序.最终增加缩小到1的时候,排序结束.所以希尔排序又叫缩小增量排序(Diminishing Increment Sort) 关于增量最佳增量值的选择其实是个数学难题,有兴趣的可以自己搜下相关资料.常用的增量有 n/2(这个又叫希尔增量).n/3.2^k-1(hibba

【数据结构与算法】内部排序之一:插入排序和希尔排序的N中实现(不断优化,附完整源码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/20043459   前言 本来想将所有的内部排序总结为一篇博文,但是随着研究的深入,还是放弃了这个念头,斟前酌后,还是觉得分开来写比较好,具体原因,看完本篇博文也就自然明了了. 本篇文章主要探讨插入排序和希尔排序,之所将二者放在一起,很明显,是因为希尔排序是建立在插入排序的基础之上的.     注:以下各排序算法的N种实现方法大部分都是我根据算法思想,自己写出来的,或者是参考其本身的经典实

希尔排序(插入排序)-八大排序三大查找汇总(5)

基本思想 该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序. 稳定性 由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的. 时间复杂度 希尔排序的时间复杂度取决于步长的选择. 平均情况下,

插入排序---希尔插入排序算法(Javascript版)

取一个小于n的整数作为第一个增量,把序列分组.所有距离为增量的倍数的元素放在同一个组中.先在各组内进行直接插入排序:然后,取第二个增量(第二个<第一个)重复上述的分组和排序,直至所取的增量=1,即所有元素放在同一组中进行直接插入排序为止. 一般的初次取序列的一半为增量,以后每次减半,直到增量为1. 以下代码在nodejs中执行通过. function shellInsertSort(elements, di){ //从增量的所在位置开始 for(var i = di; i < elements

内部排序-&gt;插入排序-&gt;希尔排序

文字描述 希尔排序又称缩小增量排序,也属于插入排序类,但在时间效率上较之前的插入排序有较大的改进. 从之前的直接插入排序的分析得知,时间复杂度为n*n, 有如下两个特点: (1)如果待排序记录本身就是"正序"时, 其时间复杂度可减少为n. (2)当待排序记录数很小时,直接插入排序的效率也比较高; 希尔排序正是从这两点分析出发对直接插入排序进行了改进.它的基本思想是:先将整个待排记录序列分割成为若干个子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全

选择排序,插入排序以及希尔排序

1. 选择排序 首先,找到数组中最小的那个元素; 将它与数组中的第一个元素交换位置; 在剩下的数组中找到最小的元素,和数组的第二个元素交换位置,如此循环往复; public class Selection{ // 将数组a按升序排列 public static void sort(Comparable[] a){ int N = a.length; for(int i = 0; i < N; i++){ int min = i; for(int j = i + 1; j < N; j++){