排序一般是指将表中的元素按关键字递增或递减的过程排列。排序的确切定义如下:
输入:n个记录R1, R2, ...... Rn, 对应的关键值为k1, k2 ........ kn。
输出:一个重排的序列R1', R2' ....... Rn', 使得k1'<=k2'<=.....kn'。
排序算法有一个算法稳定性的性质。算法稳定性就是指:在排序前有两个元素Ri ,Rj,他们的关键字ki,kj,且ki=kj。如果在排序前Ri在Rj的前面,在排序后Ri仍然在Rj的前面,就说这个排序算法是稳定的。但是并不是说稳定的算法就是好的算法,这只是排序算法的一个性质而已。
排序算法的分类:首先排序分为内部排序(所有元素在内存)和外部排序(有元素在外存,涉及元素内外存的移动)。
内部排序有:插入排序(直接插入,折半插入,希尔排序),交换排序(冒泡排序,快速排序),选择排序(简单选择排序,堆排序),归并排序,基数排序。
下面介绍直接插入排序,折半插入排序,以数组为例:
直接插入排序的思想是每次讲一个待排序的元素按照其大小插入到前面已经排好序的子序列中,直到元素全部插入。
已经排好序的序列 L(1 ~ i-1) | 待排序的元素L(i) | 未排好序的序列L(i+1 ~ n) |
为了将待排序元素正确插入到已排序序列中,我们有以下操作:
1. 比较L[i]与L[i-1],如果L[i]<L[i-1],说明L[i]需要放到前面的有序序列中,执行下一步。
2.根据L[i]的值找到它应该插入的位置,假设为L[k]。
3.将L(k ~ i-1)中的元素全部往后移一个,给L[k]腾出位置。
4.将L[i]的值拷贝到L[k]。
下面是排序的代码,以数组为例:
void InsertSort(int A[],int n)
{
int i,j,m;
for(int i=1;i<n;i++) //把A[0]看成一个有序序列,将A[1]~A[n-1]往前插
{
if(A[i]<A[i-1]) //如果待排元素小于前一个元素,则往前插
{
m=A[i]; //用一个变量记录待插元素的值
for(j=i-1;A[j]>m;--j) //从i-1处往前寻找,对每个元素比较一次都将这个元素后移。当找到A[j]<m,说明j+1就是要插入的位置,直接跳出循环
{
A[j+1]=A[j]; //不满足大小关系的元素后移
}
A[j+1]=m; //将待插入元素的值复制到该插入的位置
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1)。
折半插入排序:思想与直接插入排序相似。区别是直接插入排序是边比较边移动元素,折半插入排序则是将比较和插入的操作分离出来。先通过折半查找找到元素该插入的位置,再统一将该位置及后面的元素后移。代码如下:
void InsertSort(int A[],int n)
{
int i,j,low,high,mid,m;
for(i=1;i<n;i++)
{
m=A[i];
low=0;high=i-1;
while(low<=high) //先比较,寻找位置
{
mid=(low+high)/2;
if(A[mid]>m) high=mid-1; //查找左半段
else low=mid+1; //查找右半段
} //跳出循环时,说明插入的位置在mid,也就是high+1。
for(j=i-1;j>=high+1;--j) //后统一移动,腾出位置
{
A[j+1]=A[j];
}
A[high+1]=m; //拷贝待插入的元素
}
}
可以看出,折半插入将比较元素寻找位置和移动元素的操作分开了。时间复杂度:O(n^2) 空间复杂度:O(1)
从排序思想可以看出,这两个算法都是稳定的算法。
大都数排序算法只适用于顺序存储的表,而直接插入排序可用于顺序存储的表排序也可用于链式存储的表排序。折半插入排序只适用于顺序存储的表的排序。
一般数据量较小的表中,折半插入排序的性能更优。