根据排序过程中数据元素是否完全在内存中分为两类:内部排序和外部排序。
排序主要基于三个要素进行对比:时间复杂度,空间复杂度和算法的稳定性。
1.插入排序是一种简单直观的排序算法,其思想在于每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的合适位置,知道全部记录插入完成为止。一趟排序后,并没有是一个记录到达其最终位置,这是插入类排序方法的共同的特点。
1.1直接插入排序
void InsertSort(ElemType A[],int n)
{
int i,j;
for(i=2;i<=n;i++)//数组从下标1开始存储,第一个元素有序,一次将A[i]插入到前面已排序序列
if(A[i].key<A[i-1].key)
{
A[0]=A[i];//复制为哨兵
for(j=i-1;A[0].key<A[j].key;j--)//这个循环完成了从待排元素之前的元素开始少苗,如果大于待排元素则后移一位
{
A[j+1]=A[j];//向后移位
}
A[j+1]=A[0];//找到插入位置,将A[0]中暂存的待排元素插入
}
}
时间复杂度分析:
由于插入排序算法代码,可以选取最内层循环里的A[j+1]=A[j];这一句作为基本操作。
(1).考虑最坏的情况,即整个序列是逆序的,则内层循环中A[0].key<A[j].key;这个条件实施中城里的,吃屎对于没一次外层循环,最内层循环的执行次数,也是基本操作的执行次数都达到最大值,为i次(比如当外层循环进行到i等于5时,内层循环j取从4到1,执行4次)。i取值范围为2~n,得基本操作总的执行次数n*(n-1)/2.显然时间复杂度O(n*n);
(2)考虑最好的情况,即整个序列已经有序,则对于内层循环中A[0].key<A[j].key;这个条件始终不成立,吃屎内层循环始终不执行,双层循环就变成了单层循环,循环内的操作都为常量级,显然时间复杂度O(n);
直接插入排序的平均时间复杂度:O(n*n);
空间复杂度:O(1);
稳定
直接插入排序既适用于顺序存储的线性表,也适用于链式存储的线性表。
1.2.折半插入排序
折半插入排序的基本原理与直接插入排序相同,不同之处在于,确定当前记录在前面有序子数组中的位置是,直接插入排序是采用顺序查找法,而折半插入排序是采用折半查找法,因此它仅适用于顺序存储的线性表。
在直接插入排序中,总是边比较边移动元素,而折半插入排序则将比较和移动操作分离,即先查找出元素的带插入位置,然后在统一的移动带插入位置之后的所有元素。
void InsertSort1(Elemtype A[],int len)
{
int i,j,low,high,mid;
for(i=2;i<=len;i++)
{
A[0]=A[i];
low=1;high=i-1;
while(low<=high){
mid=(low+high)/2;
if(A[mid].key>A[0].key)high=mid-1;
else low=mid+1;
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j];
A[high+1]=A[0];
}
折半插入排序适合记录数较多的场景,与直接插入排序相比,折半插入排序在寻找插入位置上面所花的时间大大减少,但是折半插入排序在记录移动次数方面和直接插入排序是一样的,折半插入排序的记录比较次数和初始序列无关。比较次数是一定的。
折半插入排序的平均时间复杂度:O(n*n);
空间复杂度:O(1);
稳定
1.3.希尔排序
又称为缩小增量排序,基本思想是,先将待排序序列分隔成若干个形如L[i,i+d,i+2d...i+kd]的特殊子表,分别进行直接插入排序,当整个表中元素已呈“基本有序”时,再对全体记录进行一次直接插入排序,由于直接插入排序算法简单,则在n值很小时效率比较高。希尔排序正是从这个两点分析出发对直接插入排序进行改进得到的一种排序方法。
void ShellSort(ElemType A[],int len)
{
for(dk=len/2;dk>1;dk=dk/2)
for(i=dk+1;i<=len;++i)
{
if(A[i].key<A[i-dk].key){
A[0]=A[i];
for(j=i-dk;j>0&&A[0].key<A[j].key;j-=dk)
A[j+dk]=A[j];
}
}
希尔排序的平均时间复杂度:O(nlogn)~O(n*n);
空间复杂度:O(1);
不稳定