数据结构(c++)学习笔记--排序


一、快速排序

1.轴点

1.1 分而治之

  • 轴点(pivot):左侧/右侧的元素,均不比它更大/更小
  • 以轴点为界,自然划分:max( [0, mi) ) ≤ min( (mi, hi) )
  • 前缀、后缀各自(递归)排序之后,原序列自然有序
    • sorted(S) = sorted(SL) + sorted(SR)
  • 归并排序(mergesort)难点在于合,而快速排序(quicksort)在于分

ntUaJ.png

1.2 快速排序

template <typename T> void Vector<T>::quickSort( Rank lo, Rank hi ) { 
    if ( hi - lo < 2 ) return; 
    Rank mi = partition( lo, hi ); 
    quickSort( lo, mi ); 
    quickSort( mi + 1, hi ); 
}

1.3 轴点

  • 必要条件: 轴点必定已然就位
  • 特别地: 在有序序列中,所有元素皆为轴点,反之亦然
  • 快速排序: 就是将所有元素逐个转换为轴点的过程
  • 絮乱(derangement): 任何元素都不在原位。比如,顺序序列循环移位
  • 不需很多交换,即可使任一元素转为轴点

2.快速划分:LUG版

2.1 减而治之,相向而行

  • 任取一个候选者(如[0])

  • L + U + G

  • 交替地向内移动lo和hi

  • 逐个检查当前元素: 若更小/大,则转移归入L/G

  • 当lo = hi时,只需 将候选者嵌入于L、G之间,即成轴点

  • 各元素最多移动一次(候选者两次) ——累计O(n)时间、O(1)辅助空间

nt5Al.png

2.2 快速划分:LUG版

template <typename T> Rank Vector<T>::partition( Rank lo, Rank hi ) { //[lo, hi) 
    swap( _elem[ lo ], _elem[ lo + rand() % ( hi - lo ) ] ); //随机交换 
    hi--; T pivot = _elem[ lo ]; //经以上交换,等效于随机选取候选轴点 
    while ( lo < hi ) { //从两端交替地向中间扫描,彼此靠拢 
        while ( lo < hi && pivot <= _elem[ hi ] ) hi--; //向左拓展G 
        _elem[ lo ] = _elem[ hi ]; //凡小于轴点者,皆归入L 
        while ( lo < hi && _elem[ lo ] <= pivot ) lo++; //向右拓展L 
        _elem[ hi ] = _elem[ lo ]; //凡大于轴点者,皆归入G 
    } //assert: lo == hi 
    _elem[ lo ] = pivot; return lo; //候选轴点归位;返回其秩 
}

2.3 不变性 + 单调性:L ≤ pivot ≤ G; U = [lo, hi]中,[lo]和[hi]交替空闲

ntTs2.png

2.4 实例

  • 线性时间:尽管lo、hi交替移动,累计移动距离不过O(n)
  • in-place只需O(1)附加空间
  • unstable
    • lo/hi的移动方向相反
    • 左/右侧的大/小重复元素可能前/后颠倒

ntXkD.png

3.迭代、贪心与随机

3.1 空间复杂度 ~ 递归深度

  • 最好:划分总均衡 O ( log ⁡ n ) O(\log n) O(logn)
  • 最差:划分皆偏侧 O ( n ) O(n) O(n)
  • 平均:均衡不致太少 O ( log ⁡ n ) O(\log n) O(logn)

3.2 非递归 + 贪心

#define Put( K, s, t ) { if ( 1 < (t) - (s) ) { K.push(s); K.push(t); } } 

#define Get( K, s, t ) { t = K.pop(); s = K.pop(); } 

template <typename T> void Vector<T>::quickSort( Rank lo, Rank hi ) { 
    Stack<Rank> Task; Put( Task, lo, hi ); //等效于对递归树的先序遍历 
    while ( ! Task.empty() ) { 
        Get( Task, lo, hi ); Rank mi = partition( lo, hi ); 
        if ( mi-lo < hi-mi ) { Put( Task, mi+1, hi ); Put( Task, lo, mi ); } 
        else { Put( Task, lo, mi ); Put( Task, mi+1, hi ); } 
    } //大|小任务优先入|出栈,可保证(辅助栈)空间不过O(logn) 非递归 
}

3.3 时间性能 + 随机

  • 最好情况:每次划分都(接近)平均,轴点总是(接近)中央 —— 到达下界
    • T ( n ) = 2 ⋅ T ( ( n − 1 ) / 2 ) + O ( n ) = O ( n ⋅ log ⁡ n ) T(n)=2·T((n-1)/2)+O(n)=O(n·\log n) 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)
  • 采用随机选取(Randomization)、三者取中(Sampling)之类的策略,只能降低最坏情况的概率,而无法杜绝

4.递归深度

4.1 居中 + 偏侧

  • 最坏情况( Ω ( n ) Ω(n) Ω(n)递归深度),概率极低
  • 平均情况( O ( log ⁡ n ) O(\log n) O(logn)递归深度),概率极高

ntR9S.png

  • 实际上:除非过于侧偏的pivot,都会有效地缩短递归深度
  • 准居中:pivot落在宽度为λ·n的居中区间 (λ也是这种情况出现的概率)
  • 每一递归路径上,至多出现 log ⁡ 2 / ( 1 + λ ) n \log_{2/(1+λ)}n log2/(1+λ)n个准居中的pivots

4.2 期望深度

  • 每递归一层,都有λ(1-λ)的概率准居中(准偏侧)
  • 深入 ( 1 / λ ) ⋅ log ⁡ 2 / ( 1 + λ ) n (1/λ)·\log_{2/(1+λ)}n (1/λ)log2/(1+λ)n层后,即可期望出现 log ⁡ 2 / ( 1 + λ ) n \log_{2/(1+λ)}n log2/(1+λ)n次准居中,且有极高的概率出现
  • 相反情况的概率 < ( 1 − λ ) ( 1 / λ − 1 ) ⋅ log ⁡ 2 / ( 1 + λ ) n = n ( 1 / λ − 1 ) ⋅ log ⁡ 2 / ( 1 + λ ) ( 1 − λ ) <(1-λ)^{(1/λ -1)·\log_{2/(1+λ)}n}=n^{(1/λ -1)·\log_{2/(1+λ)}(1-λ)} <(1λ)(1/λ1)log2/(1+λ)n=n(1/λ1)log2/(1+λ)(1λ),且随着λ增加而下降,比如λ>1/3之后,即至少有 1 − n 2 ⋅ log ⁡ 3 / 2 ( 2 / 3 ) = 1 − n − 2 1-n^{2·\log_{3/2}(2/3)}=1-n^{-2} 1n2log3/2(2/3)=1n2的概率,使得递归深度不超过 ( 1 / λ ) ⋅ log ⁡ 2 / ( 1 + λ ) n = 3 ⋅ log ⁡ 3 / 2 n ≈ 5.129 ⋅ log ⁡ n (1/λ)·\log_{2/(1+λ)}n=3·\log_{3/2}n≈5.129·\log n (1/λ)log2/(1+λ)n=3log3/2n5.129logn

5.比较次数

5.1 递推分析

  • 记期望的比较次数为T(n):T(1)=0,T(2)=1

ntYaL.png

T ( n ) = ( n − 1 ) + ( 1 / n ) ⋅ ∑ k = 0 n − 1 [ T ( k ) + T ( n − k − 1 ) ] = ( n − 1 ) + ( 2 / n ) ⋅ ∑ k = 0 n − 1 T ( k ) T(n)=(n-1)+(1/n)·\sum_{k=0}^{n-1}[T(k)+T(n-k-1)]=(n-1)+(2/n)·\sum_{k=0}^{n-1}T(k) T(n)=(n1)+(1/n)k=0n1[T(k)+T(nk1)]=(n1)+(2/n)k=0n1T(k)

n ⋅ T ( n ) = n ⋅ ( n − 1 ) + 2 ⋅ ∑ k = 0 n − 1 T ( k ) n·T(n)=n·(n-1)+2·\sum_{k=0}^{n-1}T(k) nT(n)=n(n1)+2k=0n1T(k)

( n − 1 ) ⋅ T ( n − 1 ) = ( n − 2 ) ⋅ ( n − 1 ) + 2 ⋅ ∑ k = 0 n − 2 T ( k ) (n-1)·T(n-1)=(n-2)·(n-1)+2·\sum_{k=0}^{n-2}T(k) (n1)T(n1)=(n2)(n1)+2k=0n2T(k)

n ⋅ T ( n ) − ( n + 1 ) ⋅ T ( n − 1 ) = 2 ⋅ ( n − 1 ) n·T(n)-(n+1)·T(n-1)=2·(n-1) nT(n)(n+1)T(n1)=2(n1)

T ( n ) / ( n + 1 ) − T ( n − 1 ) / n = 4 / ( n + 1 ) − 2 / n T(n)/(n+1)-T(n-1)/n=4/(n+1)-2/n T(n)/(n+1)T(n1)/n=4/(n+1)2/n

T ( n ) / ( n + 1 ) = T ( n ) / ( n + 1 ) − T ( 1 ) / 2 = 4 ⋅ ∑ k = 2 n 1 / ( k + 1 ) − 2 ⋅ ∑ k = 2 n 1 / k = 2 ⋅ ∑ k = 2 n + 1 1 / k + 2 / ( n + 1 ) − 4 ≈ 2 ⋅ ln ⁡ n T(n)/(n+1)=T(n)/(n+1)-T(1)/2=4·\sum_{k=2}^n1/(k+1)-2·\sum_{k=2}^n1/k=2·\sum_{k=2}^{n+1}1/k+2/(n+1)-4≈2·\ln n T(n)/(n+1)=T(n)/(n+1)T(1)/2=4k=2n1/(k+1)2k=2n1/k=2k=2n+11/k+2/(n+1)42lnn

T ( n ) ≈ 2 ⋅ n ⋅ ln ⁡ n = ( 2 ⋅ ln ⁡ 2 ) ⋅ n log ⁡ n ≈ 1.386 ⋅ n log ⁡ n T(n)≈2·n·\ln n=(2·\ln 2)·n\log n≈1.386 ·n\log n T(n)2nlnn=(2ln2)nlogn1.386nlogn

5.2 后向分析(Backward Analysis)

  • 设经排序后得到的输出序列为: { a 0 , a 1 , . . . , a i , . . . , a j , . . . , a n − 1 } \{a_0,a_1,...,a_i,...,a_j,...,a_{n-1}\} {a0,a1,...,ai,...,aj,...,an1}

  • 这一输出与具体使用何种算法无关,故可使用Backward Analysis

  • 比较操作的期望次数应为 T ( n ) = ∑ i = 0 n − 2 ∑ j = i + 1 n − 1 P r ( i , j ) T(n)=\sum_{i=0}^{n-2}\sum_{j=i+1}^{n-1}Pr(i,j) T(n)=i=0n2j=i+1n1Pr(i,j),亦即,每一对 < a i , a j > <a_i,a_j> <ai,aj>在排序过程中接受比较之概率的总和

  • quickSort的过程及结果,可理解为:按某种次序,将各元素逐个确认为pivot

  • k ∈ [ 0 , i ) ∪ ( j , n ) k∈[0,i)∪(j,n) k[0,i)(j,n),则 a k a_k ak早于或晚于 a i a_i ai a j a_j aj被确认,均与Pr(i,j)无关

  • 实际上, < a i , a j > <a_i,a_j> <ai,aj>接受比较,当且仅当在 { a i , a i + 1 , . . . , a j − 1 , a j } \{a_i,a_{i+1},...,a_{j-1},a_j\} {ai,ai+1,...,aj1,aj} 中, a i a_i ai a j a_j aj率先被确认

T ( n ) = ∑ i = 0 n − 2 ∑ j = i + 1 n − 1 P r ( i , j ) = ∑ j = 1 n − 1 ∑ i = 0 j − 1 P r ( i , j ) = s u m j = 1 n − 1 ∑ d = 0 j 2 / ( d + 1 ) ≈ ∑ j = 1 n − 1 2 ⋅ ( ln ⁡ j − 1 ) ≤ 2 ⋅ n ln ⁡ n T(n)=\sum_{i=0}^{n-2}\sum_{j=i+1}^{n-1}Pr(i,j)=\sum_{j=1}^{n-1}\sum_{i=0}^{j-1}Pr(i,j)=sum_{j=1}^{n-1}\sum_{d=0}^{j}2/(d+1)≈\sum_{j=1}^{n-1}2·(\ln j-1)≤2·n\ln n T(n)=i=0n2j=i+1n1Pr(i,j)=j=1n1i=0j1Pr(i,j)=sumj=1n1d=0j2/(d+1)j=1n12(lnj1)2nlnn

5.3 对比

#compare#move (对实际性能影响更大)in-placed
Quicksort平均 O ( 1.386 ∗ n log ⁡ n ) O(1.386*n\log n) O(1.386nlogn) 且高概率接近平均不超过 O ( 1.386 ∗ n log ⁡ n ) O(1.386*n\log n) O(1.386nlogn) 且实际更少
Mergesort严格 O ( 1.00 ∗ n log ⁡ n ) O(1.00*n\log n) O(1.00nlogn)严格 O ( 1.00 ∗ n log ⁡ n ) O(1.00*n\log n) O(1.00nlogn) 实际往往加倍

6.快速划分:DUP版

6.1 重复元素

  • 大量甚至全部元素重复时

    • 轴点位置总是接近于lo
    • 子序列的划分极度失衡
    • 二分递归退化为线性递归
    • 递归深度接近于O(n)
    • 运行时间接近于O(n2)
  • 移动lo和hi的过程中,同时比较相邻元素,若属于相邻的重复元素,则不再深入递归

  • 但一般情况下,如此计算量反而增加,得不偿失

6.2 算法

template <typename T> Rank Vector<T>::partition( Rank lo, Rank hi ) { //[lo, hi) 
    swap( _elem[ lo ], _elem[ lo + rand() % ( hi – lo ) ] ); //随机交换 
    hi--; T pivot = _elem[ lo ]; //经以上交换,等效于随机选取候选轴点 
    while ( lo < hi ) { //从两端交替地向中间扫描,彼此靠拢 
        while ( lo < hi ) 
            if ( pivot < _elem[ hi ] ) hi--; //向左拓展G,直至遇到不大于轴点者 
            else { _elem[ lo++ ] = _elem[ hi ]; break; } //将其归入L 
        while ( lo < hi ) 
            if ( _elem[ lo ] < pivot ) lo++; //向右拓展L,直至遇到不小于轴点者 
            else { _elem[ hi-- ] = _elem[ lo ]; break; } //将其归入G 
    } //assert: lo == hi 
    _elem[ lo ] = pivot; return lo; //候选轴点归位;返回其秩 
}

6.3 性能

  • 可以正确地处理一般情况,同时复杂度并未实质增高

  • 处理重复元素时

    • lo和hi会交替移动
    • 二者移动的距离大致相当,轴点最终被安置于(lo+hi)/2处
  • 由LUG版的勤于拓展、懒于交换,转为懒于拓展、勤于交换

  • 交换操作有所增加,更不稳定

ntoGt.png

7.快速划分:LGU版

7.1 不变性

  • S = [ l o , h i ) = [ l o ] + ( l o , m i ] + ( m i , k ) + [ k , h i ) = p i v o t + L + G + U , L < p i v o t < G S=[lo,hi)=[lo]+(lo,mi]+(mi,k)+[k,hi)=pivot+L+G+U,L<pivot<G S=[lo,hi)=[lo]+(lo,mi]+(mi,k)+[k,hi)=pivot+L+G+U,L<pivot<G

nt3Hq.png

7.2 单调性

  • pivot <= S[ k ] ? k++ : swap( S[ ++mi ], S[ k++ ] ) [k]不小于轴点 ? 直接 G 拓展 : G 滚动后移, L 拓展

7.3 算法

template <typename T> Rank Vector<T>::partition( Rank lo, Rank hi ) { //[lo, hi) 
    swap( _elem[ lo ], _elem[ lo + rand() % ( hi – lo ) ] ); //随机交换 
    T pivot = _elem[ lo ]; int mi = lo; 
    for ( Rank k = lo + 1; k < hi; k++ ) //自左向右考查每个[k] 
        if ( _elem[ k ] < pivot ) //若[k]小于轴点,则将其 
            swap( _elem[ ++mi ], _elem[ k ]); //与[mi]交换,L向右扩展 
    swap( _elem[ lo ], _elem[ mi ] ); //候选轴点归位(从而名副其实) 
    return mi; //返回轴点的秩 
}

7.4 实例

ntIAm.png

二、选取

1.众数

1.1 选取 + 中位数

  • 中位数(median):长度为n的有序序列S中,元素 S [ ⌊ n / 2 ⌋ ] S[\lfloor n/2 \rfloor] S[⌊n/2⌋]称作中位数

ntmEQ.png

  • 中位数是k-选取的一个特例,也是其中难度最大者

1.2 众数(Majority)

  • 无序向量中,若有一半以上元素同为m,则称之为众数
  • 必要性:众数若存在,则亦必中位数
  • 事实上,只要能够找出中位数,即不难验证它是否众数
template <typename T> bool majority( Vector<T> A, T & maj ) { 
    return majEleCheck( A, maj = median( A ) ); 
}

1.3 必要条件

  • mode():众数若存在,则亦必频繁数
template <typename T> bool majority( Vector<T> A, T & maj ) { 
    return majEleCheck( A, maj = mode( A ) ); 
}
  • 同样地,mode()算法难以兼顾时间、空间的高效
  • 可行思路:借助更弱但计算成本更优的必要条件,选出唯一的候选者
template <typename T> bool majority( Vector<T> A, T & maj ) { 
    return majEleCheck( A, maj = majEleCandiate( A ) ); 
}

1.4 减而治之

  • 若在向量A的前缀P(|P|为偶数)中,元素 x 出现的次数恰占半数,则A有众数,仅当对应的后缀 A − P 有众数m,且m就是 A 的众数

ntxI3.png

  • 既然最终总要花费O(n)时间做验证,故而只需考虑A的确含有众数的两种情况:
    • 若x = m,则在排除前缀 P 之后,m与其它元素在数量上的差距保持不变
    • 若x ≠ m,则在排除前缀 P 之后,m与其它元素在数量上的差距不致缩小

1.5 算法

template <typename T> T majCandidate( Vector<T> A ) { 
    T maj; 
    for ( int c = 0, i = 0; i < A.size(); i++ ) 
        if ( 0 == c ) { 
            maj = A[i]; c = 1; 
    } else maj == A[i] ? c++ : c--; 
    return maj; 
}

2.中位数

2.1 归并向量的中位数

  • 任给有序向量 S 1 S_1 S1 S 2 S_2 S2,长度 n 1 n_1 n1 n 2 n_2 n2
  • 蛮力: 经归并得到有序向量S取 S [ ( n 1 + n 2 ) / 2 ] S[(n_1+n_2)/2] S[(n1+n2)/2]即是

ntA1y.png

  • 如此,共需 O ( n 1 + n 2 ) O(n_1+n_2) O(n1+n2)时间,但毕竟未能充分利用 S 1 S_1 S1 S 2 S_2 S2的有序性
  • 以下,先解决 n 1 = n 2 n_1=n_2 n1=n2的情况,依然采用减而治之策略

2.2 等长子向量

ntLlO.png

  • 考查: m 1 = S 1 [ ⌊ n / 2 ⌋ ] m_1=S_1[\lfloor n/2 \rfloor] m1=S1[⌊n/2⌋] m 2 = S 2 [ ⌊ n / 2 ⌋ − 1 ] = S 2 [ ⌊ ( n − 1 ) / 2 ⌋ ] m_2=S_2[\lfloor n/2 \rfloor-1]=S_2[\lfloor (n-1)/2 \rfloor] m2=S2[⌊n/21]=S2[⌊(n1)/2⌋]
  • m 1 = m 2 m_1=m_2 m1=m2,则它们同为 S 1 S_1 S1 S 2 S_2 S2 S S S的中位数
  • m 1 < m 2 m_1<m_2 m1<m2,则n无论偶奇,必有: m e d i a n ( S 1 ∪ S 2 ) = , m e d i a n ( S 1 . s u f f i x ( ⌈ n / 2 ⌉ ) ∪ S 2 . p r e f i x ( ⌈ n / 2 ⌉ ) ) median(S_1∪S_2)=,median(S_1.suffix(\lceil n/2 \rceil)∪S_2.prefix(\lceil n/2 \rceil)) median(S1S2)=,median(S1.suffix(⌈n/2⌉)S2.prefix(⌈n/2⌉)),这意味着,如此减除(一半规模)之后,中位数不变
  • m 1 > m 2 m_1>m_2 m1>m2时同理
template <typename T> //尾递归,可改写为迭代形式 
T median( Vector<T> & S1, int lo1, Vector<T> & S2, int lo2, int n ) { 
    if ( n < 3 ) return trivialMedian( S1, lo1, n, S2, lo2, n ); //递归基 
    int mi1 = lo1 + n/2, mi2 = lo2 + (n - 1)/2; //长度减半 
    if ( S1[ mi1 ] < S2[ mi2 ] ) //取S1右半、S2左半 
        return median( S1, mi1, S2, lo2, n + lo1 - mi1 ); 
    else if ( S1[ mi1 ] > S2[ mi2 ] ) //取S1左半、S2右半 
        return median( S1, lo1, S2, mi2, n + lo2 - mi2 ); 
    else 
        return S1[ mi1 ]; 
}

2.3 任意子向量

template <typename T> 
T median ( Vector<T> & S1, int lo1, int n1, Vector<T> & S2, int lo2, int n2 ) { 
    if ( n1 > n2 ) 
        return median( S2, lo2, n2, S1, lo1, n1 ); //确保n1 <= n2 
    if ( n2 < 6 ) 
        return trivialMedian( S1, lo1, n1, S2, lo2, n2 ); //递归基 
    if ( 2 * n1 < n2 ) 
        return median( S1, lo1, n1, S2, lo2 + (n2-n1-1)/2, n1+2-(n2-n1)%2 );
    int mi1 = lo1 + n1/2, mi2a = lo2 + (n1 - 1)/2, mi2b = lo2 + n2 - 1 - n1/2; 
    if ( S1[ mi1 ] > S2[ mi2b ] ) //取S1左半、S2右半 
        return median( S1, lo1, n1 / 2 + 1, S2, mi2a, n2 - (n1 - 1) / 2 ); 
    else if ( S1[ mi1 ] < S2[ mi2a ] ) //取S1右半、S2左半 
        return median( S1, mi1, (n1 + 1) / 2, S2, lo2, n2 - n1 / 2 ); 
    else //S1保留,S2左右同时缩短 
        return median( S1, lo1, n1, S2, mi2a, n2 - (n1 - 1) / 2 * 2 ); 
} //O( log(min(n1,n2)) )——可见,实际上等长版本才是难度最大的

3.快速选取(QuickSelect)

3.1 尝试:蛮力

  • 对A排序( O ( n log ⁡ n ) O(n\log n) O(nlogn)),从首元素开始,向后行进k步( O ( k ) = O ( n ) O(k) = O(n) O(k)=O(n))

nte9R.png

3.2 尝试:堆(A)

  • 将所有元素组织为小顶堆( O ( n ) O(n) O(n)),连续调用k+1次delMin() ( O ( k log ⁡ n ) O(k\log n) O(klogn))

ntaHW.png

3.3 尝试:堆(B)

L = heapify( A[0, k] ) //任选 k+1 个元素,组织为大顶堆:O(k)

for each i in (k, n) //O(n - k) 
    L.insert( A[i] ) //O(logk) 
    L.delMax() //O(logk) 
return L.getMax()

nt4LG.png

3.4 尝试:堆(C)

  • 将输入任意划分为规模为k、n-k的子集,分别组织为大、小顶堆 //O(k + (n-k)) = O(n)
while ( m < M ) //O(min(k, n – k)) 
    swap( m, M ) 
    L.percolateDown() //O(logk) 
    G.percolateDown() //O(log(n - k))
    
return m = G.getMin()

nt7qz.png

3.5 下界与最优

  • 所谓第k小,是相对于序列整体而言,所以在访问每个元素至少一次之前,绝无可能确定

ntME5.png

3.6 算法

template <typename T> void quickSelect( Vector<T> & A, Rank k ) { 
    for ( Rank lo = 0, hi = A.size() - 1; lo < hi; ) { 
    Rank i = lo, j = hi; T pivot = A[lo]; //大胆猜测
    while ( i < j ) { //小心求证:O(hi - lo + 1) = O(n) 
        while ( i < j && pivot <= A[j] ) j--; A[i] = A[j]; 
        while ( i < j && A[i] <= pivot ) i++; A[j] = A[i]; 
    } //assert: quit with i == j 
    A[i] = pivot; 
    if ( k <= i ) hi = i - 1; 
    if ( i <= k ) lo = i + 1; 
    } //A[k] is now a pivot
}

ntOZ6.png

3.7 期望性能

  • 记期望的比较次数为T(n)
    • T(1)=0,T(2)=1,…
  • 可以证明: T ( n ) = ( n − 1 ) + ( 1 / n ) ⋅ ∑ k = 0 n − 1 m a x { T ( k ) , T ( n − k − 1 ) } ≤ ( n − 1 ) + ( 2 / n ) ⋅ ∑ k = n / 2 n − 1 T ( k ) T(n)=(n-1)+(1/n)·\sum_{k=0}^{n-1}max\{T(k),T(n-k-1)\}≤(n-1)+(2/n)·\sum_{k=n/2}^{n-1}T(k) T(n)=(n1)+(1/n)k=0n1max{T(k),T(nk1)}(n1)+(2/n)k=n/2n1T(k)
  • 事实上,不难验证: T ( n ) ≤ ( n − 1 ) + ( 2 / n ) ⋅ ∑ k = n / 2 n − 1 4 k ≤ ( n − 1 ) + 3 n ≤ 4 n T(n)≤(n-1)+(2/n)·\sum_{k=n/2}^{n-1}4k≤(n-1)+3n≤4n T(n)(n1)+(2/n)k=n/2n14k(n1)+3n4n

4.线性排序(LinearSelect)

4.1 linearSelect( A, n, k )

  • 设Q为小常数

  • if ( n = |A| < Q ) return trivialSelect( A, n, k ) 递归基:序列长度|A| ≤ Q -> O ( 1 ) = O ( Q log ⁡ Q ) O(1) = O(Q\log Q) O(1)=O(QlogQ)

  • else divide A evenly into n/Q subsequences (each of size Q) 子序列划分 -> O ( n ) O(n) O(n)

  • Sort each subsequence and determine n/Q medians 子序列各自排序,并找到中位数 -> O ( n ) = Q 2 ⋅ n / Q O(n)=Q^2·n/Q O(n)=Q2n/Q

  • Call linearSelect() to find M, median of the medians 从n/Q个中位数中,递归地找到全局中位数 -> T ( n / Q ) T(n/Q) T(n/Q)

  • Let L/E/G = { x</=/>M | x ∈ A } 划分子集L/E/G,并分别计数 —— 一趟扫描足矣 -> O ( n ) O(n) O(n)

  • if (k < |L|) return linearSelect(A, |L|, k) if (k < |L|+|E|) return M return linearSelect(A+|L|+|E|, |G|, k-|L|-|E|) -> T(3n/4)

4.2 复杂度

  • T ( n ) = c ⋅ n + T ( n / Q ) + T ( 3 n / 4 ) T(n)=c·n+T(n/Q)+T(3n/4) T(n)=cn+T(n/Q)+T(3n/4)

    • m i n ( ∣ L ∣ , ∣ G ∣ ) + ∣ E ∣ ≥ n / 4 min(|L|,|G|)+|E|≥n/4 min(L,G)+En/4
    • m a x ( ∣ L ∣ , ∣ G ∣ ) ≤ 3 n / 4 max(|L|,|G|)≤3n/4 max(L,G)3n/4
  • 1 / Q + 3 / 4 < 1 1/Q+3/4<1 1/Q+3/4<1时, T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n)

三、希尔排序

1.框架+实例

1.1 希尔排序(Shellsort)

  • 将整个序列视作一个矩阵,逐列各自排序

  • 递减增量(diminishing increment)

    • 由粗到细:重排矩阵,使其更窄,再次逐列排序(h-sorting/h-sorted)
    • 逐步求精:如此往复,直至矩阵变成一列(1-sorting/1-sorted)
  • 步长序列(step sequence):由各矩阵宽度逆向排列而成的序列

    • H = { h 1 = 1 , h 2 , . . . , h k , . . . } H=\{h_1=1,h_2,...,h_k,...\} H={h1=1,h2,...,hk,...}
  • 正确性:最后一次迭代,等同于全排序

    • 1-sorted = ordered

nuqj1.png

1.2 实例

  • h 5 = 8 h_5=8 h5=8

nuNvl.png

  • h 4 = 5 h_4=5 h4=5

nuSE7.png

  • h 3 = 3 h_3=3 h3=3

nuKFP.png

  • h 2 = 2 h_2=2 h2=2

nuPrD.png

  • h 1 = 1 h_1=1 h1=1

nudpS.png

1.3 循秩访问

  • 借助一维向量足以实现矩阵重排
  • 在每步迭代中,若当前的矩阵宽度为,则 B [ i ] [ j ] = A [ i ⋅ h + j ] B[i][j]=A[i·h+j] B[i][j]=A[ih+j] A [ k ] = B [ k / h ] [ k % h ] A[k]=B[k/h][k\%h] A[k]=B[k/h][k%h]

nubyy.png

1.4 实现

template <typename T> void Vector<T>::shellSort( Rank lo, Rank hi ) { 
    // Using PS Sequence { 1, 3, 7, 15, 31, 63, 127, ..., 1073741823, ... } 
    for ( Rank d = 0x3FFFFFFF; 0 < d; d >>= 1 ) 
        for ( Rank j = lo + d; j < hi; j++ ) { //for each j in [lo+d, hi) 
            T x = _elem[j]; Rank i = j - d; 
            while ( lo <= i && _elem[i] > x ) 
                { _elem[i + d] = _elem[i]; i -= d; } 
            _elem[i + d] = x; //insert [j] into its subsequence 
        } 
} //0 <= lo < hi <= size <= 2^30
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值