[算法导论]快速排序学习

基础

看懂《算法导论》需要重温数学知识。算法实现和推导是一个数学建模过程。


原理

对于包含 n 个数的输入数组来说,快速排序是一种最坏情况时间复杂度为 O( n 2 n^2 n2) 的排序算法。虽然最坏情况的时间复杂度很差,但是快排通常是实际排序应用中最好的选择,因为它平均性能非常好,它的期望实际复杂度是O( n l g n nlgn nlgn),而且O( n l g n nlgn nlgn)中隐含的常数因子非常小,另外它还能够进行原址排序,甚至在虚存环境中也能很好地工作。

详细内容请参考《算法导论》第三版,第二部分,第七章:快速排序


实现

根据数组哨兵的选择,有两种递归方式实现。调试代码在 (github)

  • 以数组末位数值为哨兵,从左向右排序
int Partition(int array[], int start, int end) {
    int low = start - 1;
    int high = low + 1;
    int key = array[end];

    for (; high < end; high++) {
        if (array[high] <= key) {
            low++;
            if (high > low) {
                int temp = array[low];
                array[low] = array[high];
                array[high] = temp;
            }
        }
    }

    // 如果是有序数组,会出现左边都是最小的情况,要置换 partition 需要判断数据。
    int partition = low + 1;
    if (array[partition] > key) {
        int temp = array[partition];
        array[partition] = array[end];
        array[end] = temp;
    }

    return partition;
}

void qsort_end(int array[], int start, int end) {
    if (start < 0 || end <=0 || start >= end) {
        return;
    }

    int partition = Partition(array, start, end);
    if (partition >= 0) {
        qsort_end(array, start, partition - 1);
        qsort_end(array, partition + 1, end);
    }
}
  • 以数组中间数值为哨兵,从两端向中间排序
void qsort_mid(int array[], int start, int end) {
    if (start >= end) {
        return;
    }

    int high = end;
    int low = start;
    int key = array[(unsigned int)(start + end) / 2];

    while (low < high) {
        // 左边向右查找比 key 大的
        while (array[low] < key && low < end) {
            low++;
        }

        // 右边向左查找比 key 小的
        while (array[high] > key && high > start) {
            high--;
        }

        if (low <= high) {
            int temp = array[low];
            array[low] = array[high];
            array[high] = temp;
            low++;
            high--;
        }
    }

    qsort_mid(array, start, high);
    qsort_mid(array, low, end);
}

时间复杂度推导

最优情况下的时间复杂度

快速排序涉及到递归调用, 递归算法的时间复杂度公式:
T [ n ] = a T [ n b ] + f ( n ) T[n]=aT[\frac{n}{b}] + f(n) T[n]=aT[bn]+f(n)
数组共有 n n n个数值,最优的情况是每次取到的元素(哨兵)刚好平分整个数组。
此时的时间复杂度公式为: T ( n ) = 2 T [ n 2 ] + f ( n ) T(n)= 2T[\frac{n}{2}] + f(n) T(n)=2T[2n]+f(n)


第一次递归:
T ( n ) = 2 T [ n 2 ] + f ( n ) T(n)= 2T[\frac{n}{2}] + f(n) T(n)=2T[2n]+f(n)


第二次递归:令 n = n 2 n = \frac{n}{2} n=2n ,
T [ n 2 ] = 2 { 2 T [ n 4 ] + ( n 2 ) } + n = 2 2 T [ n ( 2 2 ) ] + 2 n T[\frac{n}{2}] = 2 \{2T[\frac{n}{4}] + (\frac{n}{2})\} + n = 2^2T[\frac{n}{(2^2)}] + 2n T[2n]=2{2T[4n]+(2n)}+n=22T[(22)n]+2n


第三次递归:令 n = n ( 2 2 ) n = \frac{n}{(2^2)} n=(22)n
T [ n 2 2 ] = 2 2 { 2 T [ n 2 3 ] + n 2 2 } + 2 n = 2 3 T [ n 2 3 ] + 3 n T[\frac{n}{2^2}] = 2^2\{2T[\frac{n}{2^3}] + \frac{n}{2^2}\}+2n = 2^3T[\frac{n}{2^3}]+3n T[22n]=22{2T[23n]+22n}+2n=23T[23n]+3n


m m m次递归:令 n = n 2 ( m − 1 ) n = \frac{n}{2^{\left (m-1) \right.}} n=2(m1)n
T [ n 2 m − 1 ] = 2 m T [ 1 ] + m n T[\frac{n}{2^{m-1}}] = 2^mT[1]+mn T[2m1n]=2mT[1]+mn


公式一直往下迭代,当最后数组不能再平分时,最后到 T [ 1 ] T[1] T[1],说明公式迭代完成( T [ 1 ] T[1] T[1]是常量)也就是: n 2 ( m − 1 ) = 1 \frac{n}{2^{\left (m-1) \right.}} = 1 2(m1)n=1

n = 2 ( m − 1 ) n = 2^{\left (m-1) \right.} n=2(m1) ==> ( n = 2 m n = 2^m n=2m ) ==> ( m = l o g 2 n m = log_2n m=log2n )

m = l o g 2 n m = log_2n m=log2n

T [ n 2 ( m − 1 ) ] = 2 m T [ 1 ] + m n = n + n l o g 2 n T[\frac{n}{2^{\left(m-1)\right.}}] = 2^mT[1]+mn = n + nlog_2n T[2(m1)n]=2mT[1]+mn=n+nlog2n

n n n 为元素个数,当 n ≥ 2 n \geq 2 n2

n + n l o g 2 n = n ( 1 + l o g 2 n ) = = > n l o g 2 n = = > n l g n n + nlog_2n = n(1+log_2n) ==> nlog_2n ==> nlgn n+nlog2n=n(1+log2n)==>nlog2n==>nlgn


参考

算法导论 时间复杂度分析
快速排序 及其时间复杂度和空间复杂度
算法导论------递归算法的时间复杂度求解
算法复杂度中的O(logN)底数是多少
Cmd Markdown 公式指导手册

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值