/*
《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;
}