我们再来谈谈另外一个基本排序算法,插入排序。
插入排序的思想也是十分简单。我们在排序范围(begin,end)中,选择数组下标为end的元素作为基准值,从它的前一个元素开始,向前遍历。在遍历过程中,把遍历到的值与基准值进行对比,如果比基准值大,就把这个值后移,遍历下标i前移。直到找到一个元素,比基准值小,或者遍历到数组头后结束,得到一个下标值,即插入位置。最后将基准值插入到这个下标值中。
下面我们就进行算法分析吧,数据还是1 3 2 8 0 6 总共6个数
1、第一趟,排序范围(0,1),遍历下标i指向end所指位置,即下标1,基准值为3
我们发现,a[i-1]=a[0]=1 < 3,不需要向前遍历,下标1即为插入位置
2、第二趟,排序范围(0,2),遍历下标i指向end所指位置,即下标2,基准值为2
我们发现,a[i-1] = a[1] = 3> 2,所以要将下标i前移,元素3后移,i指向1下标
a[i-1] = a[0] = 1 < 2,不需要向前遍历,下标1即为插入位置
3、第三趟,排序范围为(0,3),遍历下标i指向end所指位置,即下标3,基准值为8
我们发现,a[i-1] = a[2] = 3 < 8,不需要向前遍历,下标3即为插入位置
4、第四趟,排序范围为(0,4),遍历下标i指向end所指位置,即下标4,基准值为0
我们发现,a[i-1] = a[3] = 8> 0,所以要将下标i前移,元素8后移
a[i-1] = a[2] = 3> 0 ,所以要将下标i前移,元素3后移
a[i-1] = a[1] = 2> 0,所以要将下标i前移,元素2后移
a[i-1] = a[0] = 1> 0,所以我们要将下标i前移,元素1后移
此时,遍历指针i已经指向了数组头,即下标0,说明我是在遍历的所有元素中最小的,这时,下标0就是我们需要插入的位置
5、第五趟,排序范围为(0,5),遍历下标i指向end所指位置,即下标5,基准值为6
我们发现,a[i-1] = a[4] = 8> 6,所以要将下标i前移,元素8后移
a[i-1] = a[3] = 3 < 6,不需要向前遍历,下标4即为插入位置
经过以上5趟,排序结束,最终序列为
从以上分析我们可以知道,要完成插入排序,要有两个方法,一个用于找插入位置并且插入数据(核心),另外一个用于反复调用,扩大排序范围。与选择排序不同,插入排序是保持begin指针不动,由前往后扩大。而选择排序是保持end指针不动,由前往后缩小。以上图中的下标i,就是基准值要插入的位置!
由此,我们可以得到插入排序算法核心(insert)与入口方法(sort)
#include <stdio.h>
#define Elemtype int
//插入排序核心
void insertion(Elemtype a[] , int begin , int end)
{
//将待排序数组的最后一个元素作为基准值
Elemtype base = a[end];
//遍历指针i指向待排序数组的最后一个元素
int i = end;
//循环判断,退出条件为:1、i已经指向数组头 2、发现有比基准值小的元素
while(i > 0 && a[i-1] > base)
{
//如果进入循环体,说明当前扫描值比基准值大,则应把基准值后移
a[i] = a[i-1];
//下标前移
i--;
}
//找到插入位置后,将基准值插入
a[i] = base;
}
void sort(Elemtype a[] , int begin , int end)
{
int i;
//从第一个元素开始,到最后一个元素为止,保持begin指针不动,逐渐扩大排序范围
for(i = begin ; i <= end ; i++)
{
insertion(a,begin,i);
}
}
完整可执行程序如下
#include <stdio.h>
#define Elemtype int
//插入排序核心
void insertion(Elemtype a[] , int begin , int end)
{
//将待排序数组的最后一个元素作为基准值
Elemtype base = a[end];
//遍历指针i指向待排序数组的最后一个元素
int i = end;
//循环判断,退出条件为:1、i已经指向数组头 2、发现有比基准值小的元素
while(i > 0 && a[i-1] > base)
{
//如果进入循环体,说明当前扫描值比基准值大,则应把基准值后移
a[i] = a[i-1];
//下标前移
i--;
}
//找到插入位置后,将基准值插入
a[i] = base;
}
void sort(Elemtype a[] , int begin , int end)
{
int i;
//从第一个元素开始,到最后一个元素为止,保持begin指针不动,逐渐扩大排序范围
for(i = begin ; i <= end ; i++)
{
insertion(a,begin,i);
}
}
void display(Elemtype a[] , int begin , int end)
{
int i;
for(i = begin ; i <= end ; i++)
{
printf("%d ",a[i]);
}
printf("\n");
}
int main()
{
Elemtype a[] = {1,3,2,8,0,6};
printf("排序前:\n");
display(a,0,5);
sort(a,0,5);
printf("排序后:\n");
display(a,0,5);
return 0;
}
运行效果如下:
插入排序算法的时间复杂度也是O(n^2)