快速排序为什么快?

排序算法有很多种,如冒泡排序、插入排序、堆排序、归并排序、希尔排序、快速排序等,也体会到了它们之间的不同时间复杂度。但是之前一直没有想明白,快速排序虽然是将一个数组切分成两个数组,但是为何就要快一点呢?比较笨,一时转不过来,就记住它们的时间复杂度吧,反正也验证过,没有错的。

今天突然又想到这个问题,尝试了用一万个元素的数组分别比较了冒泡排序和快速排序,发现他们的比较次数大概遵循某些规律。

冒泡排序每次只排序一个元素(通常是最末尾的),因此一万个元素第一次需要比较 9999 次,第二个元素需要比较 9998 个,以此类推,总共需要比较 1+2+3+...+9999 次。

快速排序的伪代码:

quick_sort(n){             //数组长度为n
    定位标杆                //比较n-1次
    quick_sort(n/2)        //递归快排前n/2个元素
    quick_sort(n/2)        //递归快排后n/2个元素
}

快速排序第一次也需要比较 9999 次(将标杆定位时),但是因为其将数组分成两部分(把它们看作相等的两部分),一次快速排序分别递归调用两次快速排序,因此递归调用的两次快速排序在定位标杆时,都需要 4999 次的比较,故其规律是 1*9999+2*4999+4*2999+...+10000*1(大概)。

翻开《算法导论》第三版101页,可见快速排序的递推式为:T(n)=max(T(q) + T(n-q-1)) +O(n),q为切分长度,如果每次切分都刚好切分成两半,则 q==n-q-1T(q)==T(n-q-1) ,则简化为 T(n)=2*T(n/2)+O(n)。换一下加法项的位置,T(n)=O(n)+2*T(n/2),不正是上面的规律吗?第一次比较 9999 次,因此 T(10000)=9999+2*T(5000),而 T(5000)=4999+2*T(2500)。因此 T(10000)=9999+2*(4999+2*T(2500)),即 T(10000)=1*9999+2*4999+4*T(2500),最终得到上述分析出的规律。

注意,下面的分析都是近似。冒泡排序为 19999 的累加共 10000*5000==5000,0000,而快速排序为每一项都是 9999,近似为 10000,但是项数少很多,按照2次幂的增长,2^13==81922^14=16384,因此从 2^0==12^14==16384 最多15项,因此大约 10000*15==15,0000。冒泡排序比较次数是快速排序比较次数的约 333 倍。

写了个程序验证,下面是运行截图:
数组长度10000

数组长度100000

从图中可知,10000 个元素的数组,冒泡排序的比较次数与分析的 5000,0000 十分接近。快速排序比分析的 15,0000 多一些,但是大体相近,应该是数组长度小于20时采用了插入排序,以及数组长度切分不均匀导致的。

由此可见,快速排序比冒泡排序快在比较次数少很多次。

程序代码:

#include "stdafx.h"
#include <iostream>
#include <chrono>
using namespace std;

//注意,只记录了元素的比较次数,[大概] 地说明两种排序方法分别进行了多少次比较
//元素赋值等没有考虑,但我想应该不影响最终结果

int b_count = 0;            //统计冒泡排序中元素比较次数
int q_count = 0;            //统计快速排序中元素比较次数

//冒泡排序
void bubble_sort(int* arr, int len) {
    for (int i = len - 1; i > 0; --i) {
        for (int j = 0; j < i; ++j) {
            ++b_count;                  //++b_count
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

//插入排序,当快速排序中子数组长度小于20时调用
void insertion_sort(int* arr, int beg, int end) {
    for (int i = beg + 1; i <= end; ++i) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            ++q_count;                      //++q_count
            arr[j + 1] = arr[j];
            --j;
        }
        arr[j + 1] = key;
    }
}

//划分函数--快速排序中使用
int partition(int* arr, int beg, int end) {

    //三数中值法,可避免快排的最坏情况
    int mid = (beg + end) / 2;
    int max_index = beg;

    if (arr[mid] > arr[max_index]) max_index = mid;         //++q_count;
    if (arr[end] > arr[max_index]) max_index = end;         //++q_count;
    int index1 = max_index == beg ? mid : beg;
    int index2 = max_index == beg || max_index == mid ? end : mid;
    if (arr[index1] < arr[index2]) index1 = index2;         //++q_count;
    q_count += 3;           //统一对q_count进行递增

    int temp = arr[max_index];
    arr[max_index] = arr[index1];
    arr[index1] = temp;
    int index = beg - 1;
    int key = arr[end - 1];
    for (int i = beg; i <= end; ++i) {
        ++q_count;                      //++q_count
        if (arr[i] < key) {
            ++index;
            int temp = arr[index];
            arr[index] = arr[i];
            arr[i] = temp;
        }
    }
    ++index;
    arr[end - 1] = arr[index];
    arr[index] = key;
    return index;
}

//快速排序的主算法体,被quick_sort调用
void q_sort(int* arr, int beg, int end) {
    if (beg >= end) return;
    if (end - beg > 20) {           //当子数组元素个数大于20时递归进行快排
        int p = partition(arr, beg, end);
        q_sort(arr, beg, p - 1);
        q_sort(arr, p + 1, end);
    }
    else {                          //进行插入排序
        insertion_sort(arr, beg, end);
    }
}

//快速排序
void quick_sort(int* arr, int len) {
    q_sort(arr, 0, len - 1);
}

int main() {
    const int LENGTH = 10000;
    int arr1[LENGTH];           //用于冒泡排序
    int arr2[LENGTH];           //用于快速排序

    cout << "数组长度:" << LENGTH << endl;

    //随机生成数组元素
    auto start_time = chrono::system_clock::now();      //开始计时
    for (int i = 0; i < LENGTH; ++i) {
        int r = rand();
        arr1[i] = r;
        arr2[i] = r;
    }
    auto end_time = chrono::system_clock::now();        //结束计时
    cout << "随机生成10000个int型元素的数组耗时: " << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << "ms" << endl;

    //冒泡排序
    start_time = chrono::system_clock::now();           //开始计时
    bubble_sort(arr1, LENGTH);
    end_time = chrono::system_clock::now();             //结束计时
    cout << "冒泡排序耗时: " << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << "ms" << endl;

    //快速排序
    start_time = chrono::system_clock::now();           //开始计时
    quick_sort(arr2, LENGTH);
    end_time = chrono::system_clock::now();             //结束计时
    cout << "快速排序耗时: " << chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << "ms" << endl;

    //输出比较次数
    cout << "冒泡排序元素比较次数: " << b_count << endl;
    cout << "快速排序元素比较次数: " << q_count << endl;
    system("pause");
}

OVER

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值