排序算法总结

内部串行排序算法

一、比较策略算法

1、冒泡排序
稳定性:稳定
思想:每次调用,都会至少有一个元素就位,整体复杂度呈现算术级数的形式。若一次调用过程没有发生元素交换,则说明所有元素就位,最差和最理想情况下的复杂度为 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n),平均复杂度为 O ( n 2 ) O(n^2) O(n2)
(1)基础版本:

bool bubble(Rank lo, Rank hi){
    bool sorted = true;
    while(++lo < hi){
        if(Array[lo-1] > Array[lo]) {
            sorted = false;
            int holder = Array[lo-1];
            Array[lo-1] = Array[lo];
            Array[lo] = holder;
        }
    }
    return sorted;
}
void bubbleSort(Rank lo, Rank hi){
    while(!bubble(lo,hi--));
}

(2)优化版本:面对后缀部分元素已经就位的情况,原先的算法仍然会进行比较,因此可从此处着手改进,方法是记录上一次扫描交换进行的最后一次交换位置,将hi更新为这个值, 减少不必要的比较次数,最差和最理想情况复杂度依然是 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)

bool bubble(Rank lo, Rank hi){
    Rank last = lo ;
    while(++lo < hi){
        if(Array[lo-1] > Array[lo]) {
            last = lo;
            int holder = Array[lo-1];
            Array[lo-1] = Array[lo];
            Array[lo] = holder;
        }
    }
    return last;
}
void bubbleSort(Rank lo, Rank hi){
    while(lo< bubble(lo,hi));
}

优化版本和初始版本的对比如下图所示:
在这里插入图片描述
2、选择排序
(1)思想:依次从待排序序列中选出最小的元素放入排序序列。冒泡排序也可以看成是一种选择排序,但其最坏情况下需要 O ( n 2 ) O(n^2) O(n2),元素在转移至最终位置前往往要经过多次位置交换,选择排序则是一次交换直接将元素移动到目标位置。
(2)实现:

void selectionSort(Rank lo,Rank hi){
    Rank i,j,smallest;
    for( i = 0;i<(hi-lo-1);i++){
        smallest = i;
        for(j = i+1;j<hi;j++)
            if(Array[j]<Array[smallest]) smallest = j;
        int swap = Array[smallest];
        Array[smallest] = Array[i];
        Array[i] = swap;
    }
}

(3)性能:
空间复杂度:O(1)
在所有情况下时间复杂度:O(n^2),但其主要消耗于比较操作,冒泡排序则是主要消耗于交换操作,因此选择排序的性能要较冒泡排序高。
稳定性:稳定

(4)能否对比较操作做进一步改进?
sure.

3、插入排序
(1)思想:
使用列表进行组织(不能使用向量), 方式可类比抽扑克牌在手中排好序
(2)实现:
(3)性能:
1、时间复杂度:
最好情况下 O ( n ) O(n) O(n),最坏和平均情况下 O ( n 2 ) O(n^2) O(n2)
为求平均情况的时间复杂度,需假设各元素取值遵守均匀独立分布,采用后向分析(backward analysis)求每次插入平均需要的比较次数。
当L(r)完成插入后,是插入到了列表的什么地方呢?因为各元素取值均匀独立分布,因此,每个位置的可能性均为 1 / ( r + 1 ) 1/(r+1) 1/(r+1),而从后到前所需的比较次数从1到r递增。
因此比较成本可表示为
[ 0 + 1 + . . . + r ] / ( r + 1 ) + 1 = r / 2 + 1 [0+1+...+r]/(r+1)+1 = r/2+1 [0+1+...+r]/(r+1)+1=r/2+1,
和前缀长度呈线性关系。
将每个元素的比较成本相加,可得整体的比较成本为:
[ 0 + 1 + . . . + ( n − 1 ) ] / 2 + 1 = O ( n 2 ) [0+1+...+(n-1)]/2+1 = O(n^2) [0+1+...+(n1)]/2+1=O(n2)

2、空间复杂度: O ( 1 ) O(1) O(1)
3、稳定性:稳定

(4)插入排序与逆序对
逆序对:两个元素,左侧的元素大于右侧,则称这两个元素构成了一个逆序对。
在插入排序中,设待插入结点为P,其在排好序的序列中逆序对的个数 i ( P ) i(P) i(P)就是其在插入过程中需要经过的比较次数。
因此,整个序列中的逆序对数 ∑ P ( i ) \sum_P(i) P(i),即排序过程中比较次数总和,加上元素插入的时间总和 O ( n ) O(n) O(n)即是插入排序总共所花费的时间。
在最好情况下,无逆序对,所有元素都是顺序输入的。
而在最坏情况下,任何一对元素都是逆序对。
可以将插入排序看成是将逆序对逐渐修复的过程,因此插入排序输入对“输入敏感的”排序算法。

4、比较策略排序算法的下界:
基于比较策略的算法的比较过程可用一颗二叉树来表示,最坏时间复杂度取决于树的深度。
高度为h的二叉树,叶结点数目小于等于2^h, 排序算法的叶结点<=n,因此排序算法的树高h> =log2n ,因此基于比较策略的排序算法的复杂度下界为 l o g ( n ) log(n) log(n)

二、 分治策略算法

1、归并排序
最好和最坏的情况下,复杂度都为 O ( n l o g n ) O(nlogn) O(nlogn),稳定。
(1)思想:将待排序的序列一分为二( O ( 1 ) O(1) O(1) ),对子序列递归排序( 2 ∗ T ( n / 2 ) 2*T(n/2) 2T(n/2)),合并有序子序列(merge)。
(2)实现:

void merge(Rank lo,Rank mid , Rank hi){
    //A指向区间的起点
    int * A = Array + lo;
    //将向量的前半段复制到新开辟的空间中
    int lb = mid - lo ;int *B = new int[lb];
    for(int i = 0;i<lb;i++)B[i] = A[i];
    //C指向区间的后半段,C并不需要另辟空间缓存
    int lc = hi - mid;
    int * C = Array + mid;
    /*
     每次比较B,C两个子向量当前的首元素,
     取出其中更小的项,放入到A中
     j或k每次其中一个会加1,初始j+k = 0,最终j+k = n。
     因此迭代最多只会进行n次,merge算法时间复杂度为O(n)
     */
    for(Rank i =0,j=0,k=0;(j<lb)||(k<lc);){
        if((j<lb)&&((k>=lc)||(B[j]<=C[k]))) A[i++] = B[j++];
        if((k<lc)&&((j>=lb)||(C[k]< B[j]))) A[i++] = C[k++];
    }
    delete []B;
}
void mergeSort(Rank lo,Rank hi){
    if((hi - lo)<2 )return ;
    int mid = (hi + lo) >> 1 ;
    mergeSort(lo, mid);
    mergeSort(mid, hi);
    merge(lo,mid,hi);
    
    
}

因为merge算法的时间复杂度为 O ( n ) O(n) O(n)(见代码注释),
因此归并排序递推式为:
T ( n ) = 2 ∗ T ( n / 2 ) + O ( n ) T(n)=2*T(n/2)+O(n) T(n)=2T(n/2)+O(n)
解得
T ( n ) = O ( n l o g n ) T(n)=O(nlogn) T(n)=O(nlogn)

2、快速排序
(1)与归并排序的区别:
相同点:和归并排序一样,都是将两个排好序的子序列合二为一。
不同点:
1、对两个要合并的序列,快速排序要求Smax(L)<= Smin( R),不用merge
2、归并排序能保证划分出来的两个子任务彼此规模相当,而快速排序则不能,具体取决于候选轴点的选取
(2)轴点转化算法:

/*
将候选元素转化为轴点,方法:
将候选轴点“取出”,原位置成为“空闲单元”,
设lo与hi两个指针,初始指向序列的首元素和末元素,
hi向左移动,直到hi指向的元素比候选轴点小,则将其放入当前的“空闲单元”,hi指向的位置成为新的“空闲单元”;
lo向右移动,直到指向的元素比候选轴点大,~。
lo和hi交替移动,直到lo和hi相遇,则其位置就是轴点所在,将候选元素放入。
*/
Rank partition(Rank lo,Rank hi){
    //随机选取候选轴点
       int seq = rand()%(hi-lo) + lo;
       Rank n = Array[seq];
       Array[seq] = Array[lo];
       Array[lo] = n;
       Rank pivot = Array[lo];
    //轴点转化
    while(lo<hi){
        while((lo<hi)&&(pivot<=Array[hi]))hi--;
        Array[lo] = Array[hi] ;
        while((lo<hi)&&(pivot>=Array[lo]))lo++;
        Array[hi] = Array[lo] ;
    }
    Array[lo] = pivot;
    return lo;
}
void quicksort(Rank lo,Rank hi){
    if(hi-lo<2) return ;
    Rank mi = partition(lo,hi);
    quicksort(lo, mi);
    quicksort(mi+1, hi);
}

(3)性能分析
1、稳定性:不稳定,在轴点构造的过程中可能会颠倒元素的次序。
2、空间复杂度:可以就地实现,只需要维护常数个指针,空间复杂度O(1)
3、时间复杂度:
最优性能:每次选取的轴点都能讲序列均衡划分,此时:
T ( n ) = 2 ∗ T ( ( n − 1 ) / 2 ) + O ( n ) = O ( n l o g n ) T(n) = 2*T((n-1)/2) + O(n) = O(nlogn) T(n)=2T((n1)/2)+O(n)=O(nlogn)
最差性能:每次轴点选取的都是最大/最小的元素,此时
T ( n ) = T ( n − 1 ) + T ( 0 ) + O ( n ) = O ( n 2 ) T(n) = T(n-1)+T(0)+O(n) = O(n^2) T(n)=T(n1)+T(0)+O(n)=O(n2)
平均性能
设序列中所有元素都是均匀独立分布,设选取的候选轴点最终的秩为k,则性能表示如下:
T ( n ) = ( n + 1 ) + ( 1 / n ) × T(n)=(n+1)+(1/n)\times T(n)=(n+1)+(1/n)× ∑ k = 0 n − 1 [ T ( k ) + T ( n − k − 1 ) ] \sum_{k=0}^{n-1} [T(k)+T(n-k-1)] k=0n1[T(k)+T(nk1)]
化简得到:
T ( n ) / ( n + 1 ) = 2 / ( n − 1 ) + 2 / n + T ( n − 2 ) / ( n − 1 ) T(n)/(n+1) = 2/(n-1) + 2/n +T(n-2)/(n-1) T(n)/(n+1)=2/(n1)+2/n+T(n2)/(n1)

可将T(n)/(n+1)看成S(n),T(n-2)/(n-1)看成S(n-2),由此递推…构成调和级数
调和级数与自然对数logN同阶,因此快速排序的平均运行时间不超过 O ( n l o g n ) O(nlogn) O(nlogn)

(4)优化
快速排序算法的优化可以从两方面考虑:
1、优化候选轴点选取: 众数
2、优化轴点构造算法

(5)优化轴点选取
1、众数选取

4、希尔排序

三、 散列算法

1、桶排序

2、基数排序

3、奇数排序

四、 优先级队列

1、堆排序

2、锦标赛排序

https://play.google.com/apps/test/RQFlordB7z8/ahAIGJVrcSQNYxHGIYbG31j5ctZzYMIhwLHRyBXkaNfF1hWjOsI-P0oBIeGy-R0igQciAypFSaAdXXFhfEGOw_48xy

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值