插入排序分为以下几类:
1:直接插入排序
2:折半插入排序
3: 2-路插入排序
4:表插入排序
5:希尔排序
其中,2-4是减少比较和移动次数来提高性能。
下面一个一个介绍原理,算法实现以及性能分析:
一:直接插入排序
1:原理:
本算法是从1-n遍历进行排序,当排序第i个数据的时候,前面i-1个数据已经有序;然后将第i个数据从i-1个数据开始,依次往前比较,插入到合适的位置。
2:算法实现:
void insertion_sort1(int arr[],int n)
{
int i,j;
int key;
for(i=1;i=0;arr[j+1]=arr[j],j--){
if(arr[j]<=key){
arr[j+1]=key;
break;
}
}
}
}
void insertion_sort2(int arr[],int n)
{
int i,j;
int key;
for(i=1;i=0&&arr[j]>key){
arr[j+1]=arr[j];
j--;
}
arr[j+1]=key;
}
}
3:分析:
(1)该算法实现的是原址排序。
(2)时间复杂度:
最好情况(顺序有序):O(n)——比较次数(n-1),移动次数0。
最坏情况(逆序有序):O(n^2)——比较次数(n+2)(n+1)/2,移动次数(n+4)(n+1)/2
(3)排序是稳定的。
二:折半插入排序
1:原理
当要插入第i个数据的时候,前面i-1个数据已经有序,那么我们可以利用折半查找的方法,找到要插入的位置。然后从该位置到i-1依次移动数据。
2:实现:
void binary_insert_sort(int arr[],int n)
{
int i,j;
int key;
int high,low,mid;
for(i=1;ihigh;j--)
arr[j+1]=arr[j];
arr[j+1]=key;
}
}
(1)该算法也是原址排序,在直接插入排序的基础上减少比较次数
(2)时间复杂度:最好情况O(n);最坏情况O(n^2)
(3)注意:1、最后for循环时必须要插入high+1的位置,通过比较画图仔细分析得出。2、当key
三:2-路插入排序
1:原理
2-路插入排序在折半插入排序的基础上改进,目的是减少排序过程中移动的次数,但为此要n个记录的辅助空间。
具体思路:另设一个与含有n个数据的待排数据序列同类型的数组d,把待排序列的第一个元素(根据自己的数据结构来确定)赋值给d[0],并将d[0]看做是在排好序的序列的中间位置,然后将剩下的n-1个序列一次插入到d[0]之前或之后的有序数据序列(将数组d看做循环向量)中,这时插入利用直接插入。
实现算法的时候,要设两个指针first和final。
2:实现
void path2_insert_sort(int arr[],int n)
{
int final = 0,first = 0;
int *buf = (int *)malloc(n*sizeof(int));
for(int k=0;k=buf[j];j++)
buf[j-1]=buf[j];
buf[j-1]=arr[i];
first--;
}
}else{ //插入右边
int j;
for(j=final;j>=0 && arr[i]
3:分析
此算法相对折半插入算法来说,能减少移动记录的次数,但是不能绝对避免移动记录(除非是已排好序的数据序列),移动记录的次数约为N^2/8。根据由折半插入排序改进而来的2路插入排序,我不由自主的就想到了能不能来个2-路快速插入排序,即结合折半插入排序、快速排序优点的排序。
技巧:
(1)函数中要定义一个跟传进来的数组一样大小的数组,要么全局定义一个MAX常量,要么用动态分配的方法malloc来分配内存。
(2)如果对一个数组初始化,可以用for循环,更方便的是利用memset()函数来实现,详见:
http://blog.csdn.net/a45872055555/article/details/27187357
(3)时间复杂度依然是O(n^2)
四:表插入排序(略,以后再补)
1:原理
2:实现
3:分析
五:希尔排序(又称“缩小增量排序”)
此处参考大牛帖子:http://blog.csdn.net/morewindows/article/details/6668714
1:原理
由直接插入排序的两个优点进化而来:当待排序列基本有序时,时间复杂度降为O(n);直接插入排序算法简单,在n很小时效率高。
基本思想:先将整个待排序列分割成若干子序列分别进行插入排序,待整个记录基本有序时,对整体进行一次插入排序。
特点:子序列的构成不是简单的“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。一般选择:for(gap=n/2;gap>0;gap=gap/2);
以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例
第一次 gap = 10 / 2 = 5
49 38 65 97 26 13 27 49 55 4
1A 1B
2A 2B
3A 3B
4A 4B
5A 5B
1A,1B,2A,2B等为分组标记,数字相同的表示在同一组,大写字母表示是该组的第几个元素, 每次对同一组的数据进行直接插入排序。即分成了五组(49, 13) (38, 27) (65, 49) (97, 55) (26, 4)这样每组排序后就变成了(13, 49) (27, 38) (49, 65) (55, 97) (4, 26),下同。
排序后
13 27 49 55 4 49 38 65 97 26
第二次 gap = 5 / 2 = 2
13 27 49 55 4 49 38 65 97 26
1A 1B 1C 1D 1E
2A 2B 2C 2D 2E
排序后:
4 26 13 27 38 49 49 55 97 65
第三次 gap = 2 / 2 = 1
4 26 13 27 38 49 49 55 97 65
1A 1B 1C 1D 1E 1F 1G 1H 1I 1J
排序后:
4 13 26 27 38 49 49 55 65 97
第四次 gap = 1 / 2 = 0 结束!2:实现
方法一:下面给出严格按照定义来写的希尔排序
void shellsort1(int arr[],int n)
{
int i,j,gap;
for(gap=n/2;gap>0;gap=gap/2){ //步长逐级降低,直到gap等0时,对整个数组排序
for(i=0;i=0 && arr[k]>temp){ //利用条件相与,简化代码结构!
arr[k+gap] = arr[k];
k = k-gap;
}
arr[k+gap] = temp;
}
}
}
}
}
void shell_sort2(int arr[],int n)
{
int j,gap;
for(gap=n/2;gap>0;gap/=2){
for(j=gap;j=0 && arr[k]>temp){
arr[k+gap]=arr[k];
k=k-gap;
}
arr[k+gap]=temp;
}
}
}
}
void shellsort3(int a[], int n)
{
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2)
for (i = gap; i < n; i++)
for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
Swap(a[j], a[j + gap]);
}
附注:上面希尔排序的步长选择都是从n/2开始,每次再减半,直到最后为1。其实也可以有另外的更高效的步长选择,如果读者有兴趣了解,请参阅维基百科上对希尔排序步长的说明:
http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F
3:分析
(1).增量序列的选择
Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
① 最后一个增量必须为1;
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。
(2).Shell排序的时间性能优于直接插入排序
希尔排序的时间性能优于直接插入排序的原因:
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。
(3).稳定性
希尔排序是不稳定的。参见上述实例,该例中两个相同关键字49在排序前后的相对次序发生了变化。