排序算法总结
1.插入排序
插入排序逐个处理待排序的记录,每个新纪录与前面已排序的子序列进行比较,将它插入到子序列中正确的位置。
void InsertSort(SqList &L)
{
for(int i=1;i<L.Len;i++)
for(int j=i;(j>0)&& (L[j].key<L[j-1].key);j--)
swap(L[j],L[j-1]);
}
最好情况?已按从小到大正序排列,不进入j--的内循环中,总的比较次数为n-1次,所以时间代价为O(n)。
最坏情况?逆序排列,每次j的内循环移动到子序列的最前端,每次内循环次数为i,所以总的处理次数为∑i=O(n^2)。平均情况与最坏情况的时间复杂度相同。
2.冒泡排序
内循环从数组的底部比较到顶部,比较相邻的值,每次把最小值冒到最上面。
void BubSort(SqList &L)
{
for(i=0;i<L.Len-1;i++)
for(j=L.Len-1;j>i;j--)
if(L[j].key<L[j-1].key)
swap(L[j],L[j-1]);
}
不考虑结点的组合情况,比较次数总是i,时间代价是∑i=O(n^2)。
3.选择排序
选择排序第i次是选择数组中第i小的记录放到数组第i个位置。
void SelectSort(SqList &L)
{
for(i=0;i<L.Len-1;i++){
lowIndex=i;
for(j=L.Len-1;j>i;j--)
if(L[j].key<L[lowIndex].key) lowIndex=j;
swap(L[i],L[lowIndex]);
}
与冒泡类似,比较次数仍为O(n^2),但交换次数比冒泡少了很多。
————————————————————————————————————————————————
前面3种算法复杂度都是O(n^2),关键的瓶颈是只比较相邻元素,比较和移动只能一步一步进行。这类排序称为交换排序。
——————————————————————————————————————————————————
4.快速排序
利用分治思想。每次子序列排序先找出一个轴值,然后将小于轴值的分到左边,大于等于轴值的分到右边,然后再分别对左右两边两个子序列用同样方法。
void QuickSort(SqList &L,int i,int j)
{
pivotIndex=FindPivot(L,j);
swap(L[pivotIndex],L[j]);
k=partition(L,i,j,L[j].key);
swap(L[k],L[j]);
if(k-i>1) QuickSort(L,i,k-1);
if(j-k>1) QuickSort(L,k+1,j);
}
int partition(SqList &L,int i,int j,Elem pivot)
{
do{
while(L[i++].key<pivot);
while(r&&L[j--].key>=pivot);
swap(L[i],L[j]);
}while(i<j);
swap(L[i],L[j]);
return i;
}
快速排序最差时间代价为∑k=O(n^2),平均时间与最优时间相似,为O(nlogn)。事实上,当n很小时,快速排序是很慢的。
经验表明,最好的组合是先用快速排序将大数组分成长度小于9的小数组,再用插入排序。
5.Shell排序
也称缩小增量排序法。不像交换排序,Shell排序在不相邻的元素间比较和交换。
void ShellSort(SqList &L,int n)
{
for(i=n/2;i>2;i/=2)
for(j=0;j<i;j++)
InsertSort2(&L[j],n,i);
InsertSort2(&L,n,1);
}
void InsertSort2(SqList A,int n,int incr)
{
for(i=incr;i<n;i+=incr)
for(j=i;j>=incr && A[j].key<A[j-incr].key;j-=incr)
swap(A[j],A[j-incr]);
}
Shell排序的平均运行时间是O(n^1.5)。
6.堆排序
二叉树中子树的平衡与快速排序中对数组的分割很相似,快速排序的轴值与二叉检索树BST根结点的值作用相同。
堆排序的最佳、平均、最差执行时间均为O(nlogn)。