【算法导论】插入排序

/*
《Introduction to Algorithms(second edition)》
 chapter2,INSERTION_SORT()
date:2014-9-14
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 50

typedef struct
{
    int arr[MAX+1];
    int length;
}sortArr;

//直接插入排序
int insertionSort(sortArr *sortNum)
{
    int i = 0;
    int j = 0;
    int key = 0;

    for(j = 2; j <= sortNum->length; j++)
    {
        key = sortNum->arr[j];  //key用来存储待排序的数字
        i = j - 1;
        while((i > 0) && (sortNum->arr[i] > key))
        {
            sortNum->arr[i+1] = sortNum->arr[i];
            i--;
        }
        sortNum->arr[i+1] = key;
    }

    return 0;
}

//创建待排数组
int creatSortArray(sortArr *sortNum)
{
    int i = 0;
    int j = 0;
    char buf[4 * MAX] = "";
    char *ptr = NULL;

    printf("请输入待排序数据,以逗号分隔,以分号结束\n"
            "例:23,12,65,36,35;\n"
            "input:");
    scanf("%s", buf);

    ptr = buf;
    sortNum->arr[0] = 0;    //sortNum->arr[0]不存值用作哨兵单元
    i = 1;
    while(*ptr != ';')
    {
        sortNum->arr[i] = atoi(ptr);
        i++;

        ptr = strstr(ptr, ",");
        if(!ptr)
        {
            break;
        }
        ptr++;
    }
    sortNum->length = (i - 1);

    return 0;
}

//打印数组
int printArray(sortArr *sortNum)
{
    int i = 0;

    for(i = 1; i <= sortNum->length; i++)
    {
        printf("%d ", sortNum->arr[i]);
    }
    printf("\n");

    return 0;
}

int main()
{
    int i = 0;
    int n = 6;
    sortArr *sortNum = NULL;

    sortNum = (sortArr *)malloc(sizeof(sortArr));
    memset(sortNum, 0, sizeof(sortArr));

    creatSortArray(sortNum);

    printArray(sortNum);    //数组排序之前打印数组
    insertionSort(sortNum); //直接插入排序
    printArray(sortNum);    //数组排序之后打印数组

    free(sortNum);

    return 0;
}

以上代码的int insertionSort(sortArr *sortNum)部分为插入排序算法,该部分代码可以进行优化,考虑一下while((i > 0) && (sortNum->arr[i] > key))这句话,如果将arr[0]作为哨兵,将他的值和待排序的数字key的值相同,那么就可以把i>0删掉,每次执行到这里时只需要进行(sortNum->arr[i] > key)一次比较,占用了一个哨兵的存储空间不过大大节省了程序的执行时间。代码如下:

int advanceInsertionSort(sortArr *sortNum)
{
    int i = 0;
    int j = 0;
    int key = 0;

    for(j = 2; j <= sortNum->length; j++)
    {
        key = sortNum->arr[j];  //key用来存储待排序的数字
        sortNum->arr[0] = key;
        i = j - 1;
        while(sortNum->arr[i] > key)
        {
            sortNum->arr[i+1] = sortNum->arr[i];
            i--;
        }
        sortNum->arr[i+1] = key;
        sortNum->arr[0] = 0;
    }

    return 0;
}

时间复杂度分析

影响程序运行时间的因素有:

· 输入实例的规模

· 输入数据的分布

· 存储数据的数据结构

算法的运行时间是指在特定输入时,所执行的基本操作数。

下面分析一下在insertionSort()内部的关键步骤中每一条指令的执行时间和执行次数

《【算法导论】插入排序》

其中n = sortNum->length,tj(j=2,3,4…)表示while循环所做的测试次数,当for或者while以正常方式退出时比较部分要比循环体多执行一次。本算法总的运行时间就是每一条语句执行时间之和,如果执行一条语句耗时ci(i=1,2,3…),则该条语句总的执行时间为ci*n。设本算法的总的运行时间是T[n],则:《【算法导论】插入排序》

对于相同的输入实例,一个算法的运行时间还和输入的数据的分布相关,例如在本算法中,如果输入的数组本身是已经排好序的话,那么就会出现最佳情况,在while((i > 0) && (sortNum->arr[i] > key))一句中,当i取其初始值j-1时,均有arr[i]<= key,则对于j=2,3,4…均有tj=1,且while下面的循环体均不执行。该程序的最佳运行时间为:

《【算法导论】插入排序》

可以表示为T[n] = a*n+b,因此该算法的时间复杂度是关于n的一个线性函数,所以插入排序在最好情况下的时间复杂度为O(n)。《【算法导论】插入排序》《【算法导论】插入排序》

如果输入的数组是按逆序排序的,那么此时会出现最坏的情况,在while((i > 0) && (sortNum->arr[i] > key))一句中,我们必须将每个arr[j]与整个已经排好序的子数组的元素做比较,因而对于j=2,3,4…,有tj=j,根据高斯求和公式,其中:

《【算法导论】插入排序》《【算法导论】插入排序》《【算法导论】插入排序》   《【算法导论】插入排序》

insertionSort()的总体运行时间为:

《【算法导论】插入排序》

可以表示为T[n] = a*n^2+b*n+c,这是一个关于n的二次函数,所以插入排序在最坏的情况下他的时间复杂度为O(n^2)。《【算法导论】插入排序》

用递归的方法实现插入排序

还可以用递归的方法实现插入排序,假设待排序的数组array[n],将其分为array[1…n-1]和array[n],首先将array[1…n-1]排成有序,然后将array[n]插到已排序的array[1…n-1]中去,这样就形成了一个递归式。下面是用递归的方法实现插入排序的代码:

//递归实现直接插入排序
int recursionInsertSort(int arr[], int start, int end)
{
    int i = 0;
    int key = 0;
    int index = 0;

    index = end - 1;

    if(index > start)
    {
        recursionInsertSort(arr, start, index);
    }

    key = arr[end];

    for(i = end; i >= 0; i--)
    {
        if(arr[i-1] > key)
        {
            arr[i] = arr[i-1];
        }
        else
        {
            break;
        }
    }
    if(i != end)
    {
        arr[i] = key;
    }

    return 0;
}

点赞