排序是计算机程序设计中的一种重要操作,是把一组无序的记录按某种次序排列起来,使之有一定的次序,以便于进行数据的查找。
排序的稳定性是指若待排数据 中包含值相同的元素,其排序完毕后的相同元素的位置关系是否与排序前的次序相同。若排序前后保持一致,及此排序方式是稳定的,否则便是不稳定的。
排序分为两类,及内排序和外排序。内排序是指将待排序列全部放入内存中,并完成排序过程。适用于待排元素不是很多的情况下。外排序是指在排序过程中还要访问外存空间,当待排序列特别大时,无法全部放入内存中时,只能使用外排序。
其中内排序又分为插入排序,交换排序,选择排序,归并排序,分配排序几大类。几大类中又有具体的实现方式。其中插入排序中包含直接插入排序(简单插入排序),折半插入排序,表插入排序和希尔排序。
直接插入排序
直接插入排序又称为简单插入排序,是对在一维数组中保存的n个元素的排序,假设有数组A,要对前n个元素进行排序,则需要对A[0]~A[n-1]区间内的n个元素进行简单插入排序。
简单插入排序的思想是将待排序列看成一个包含一各有序表,和一个无序表的序列。开始时有序表只有一个元素即A[0],无序表中包含其他元素。然后每次插入时取出无序表的首个元素,将其存放于一个单元,假设为x,用x和有序表从后向前逐个比较。以确定x在有序表中的位置。将其插入,无序表减少1。循环执行,随着无序表元素不断减少,当执行n-1次时,序列A全部排序完毕。
算法描述:在直接插入排序中,第i(0<i<n)次排序时,前面的有序表包含i个元素,即A[0]~A[i-1],无序表包含n-i个元素。此次需取出元素A[i]放入x,将x与从A[i-1]开始依次与A[j]进行比较(0<=j<i),若x<A[j],则A[j]向后移动一位。直到x>=A[j]或者j<0时停止。将x插入A[j+1]即可。
下面是C语言实现的简单插入排序:
#include <stdio.h>
void InsetSort (int a[],int n){ //直接插入排序
int i,j,x; //定义两个循环变量,一个用来暂存待插入数据的存放变量x
for(i=1;i<n;i++){
if(a[i]<a[i-1]){ //若a[i]需要改变位置,将其放入x
x=a[i];
for(j=i-1;j>-1&&x<a[j];j--){ //每次用x和a[j]比较,若小于 a[j],向后移动a[j],j--。否则此j+1为待插位置,跳出循环
a[j+1]=a[j];
}
a[j+1]=x; //将x插入,进行下次循环
}
}
}
void Print (int a[],int n){ //定义一个输出函数
int i;
for(i=0;i<n;i++){
printf("%d\t",a[i]);
}
}
int main (void){ //主函数
int a[8]={42,65,80,74,28,44,36,65};
int n=8;
InsetSort(a,n);
Print(a,n);
}
}
直接插入排序需要双重循环来完成,外层循环需要执行n-1次,内层循环需平均执行(1/2)*n次,则时间复杂度为O(n^2),因为整个过程中只申请了一个单元,所以空间复杂度为O(1),并且直接插入排序是一种稳定的排序方式。
折半插入排序
直接插入排序的算法思想中每次查找待插位置时是依次将提取出来的x与变下标A[j]进行比较,这种查找方式比较浪费时间。现在将查找方式进行优化。在有序表中查找一个特定值的位置比顺序查找更节约时间的当属二分查找了。将二分查找算法与简单插入排序算法相结合,就生成了折半插入排序算法。
算法描述:及在一次比较中不断用二分有序表来确定插入的位置,即每次比较待插入的x与有序表的中间元素a[mid],并根据比较结果重新定义有序表的上下限生成有序表的子表,然后再用x与子表的中间元素a[mid]进行比较,再次定义子表,这样循环下去。直到子表中只含一个元素时,比较一次就可以确定待插位置。
下面是用C语言实现的折半插入排序算法:
#include <stdio.h>
void BinsetSort (int a[],int n){ //折半插入排序
int i,j,x,low,high,mid; //定义两个循环变量,一个用来暂存待插入数据的存放变量x ,三个下标值变量
for(i=1;i<n;i++){ //外层循环,执行n-1次
x=a[i]; //将a[i]暂存在x中
low=0; //下标变量附初值
high=i-1;
while(low<=high){ //简单的二分查找循环 ,最终找到的待插位置为a[high+1]
mid=(low+high)/2;
if(x>a[mid])
low=mid+1;
else
high=mid-1;
}
for(j=i-1;j>=high+1;j--) //将high+1到i-1的元素依次向后移动一个单位,因为a[high+1]为待插位置,所以最后一次将a[high+1]中的元素移出后结束循环
a[j+1]=a[j];
a[j+1]=x; //将x插入,进行下一次循环
}
}
void Print (int a[],int n){ //定义一个输出函数
int i;
for(i=0;i<n;i++){
printf("%d\t",a[i]);
}
}
int main (void){ //主函数
int a[8]={42,65,80,74,28,44,36,65};
int n=8;
BinsetSort(a,n);
Print(a,n);
}
对于折半插入排序来说,还是双层循环,移动元素和外层循环的次数并没有改变,所以时间复杂度还是O(n^2),空间复杂度也依然是O(1)。但是却在查找待插位置时节约了大量时间。