排序算法大杂烩之快速排序

版权声明:本文为博主原创文章,若需转载,请评论说明,并在文章显著位置标明原文出处。 https://blog.csdn.net/lishang6257/article/details/83037445

排序算法大杂烩主干文章传送门

快速排序

  • 接着主文章结构继续分析快速排序
  • 快速排序实现主流上分为两种,第一种为快排的开山鼻祖 C . A . R . H o a r e C. A. R. Hoare 在1962年给出的,第二种是后来者给出的另一种实现方案。本文给出两种并非最原始的,而是经过随机化主元后给出的改进方案。
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

void swap(int &a,int &b){int c = a;a = b;b = c;}

int partition(vector<int> &a,int low,int high)
{
    //C. A. R. Hoare
    srand(unsigned(time(NULL)));
    int j = low + rand()%(high - low + 1);
    swap(a[high],a[j]);
    j = low;
    for(int i = low;i < high;i ++){
        if(a[i] <= a[high])swap(a[i],a[j++]);
    }
    swap(a[j ],a[high]);
    return j ;
}

int partition2(vector<int>&a,int low,int high)
{
    srand(unsigned(time(NULL)));
    int j = low + rand()%(high - low + 1);
    swap(a[high],a[j]);
    int pivot = a[high];
    while(low < high){
        while(low < high && a[low] <= pivot){low ++;}
        a[high] = a[low];
        while(low < high && a[high] > pivot){high --;}
        a[low] = a[high];
    }
    cout << low << " " << high << ";\n";
    a[low] = pivot;
    return low;
}

void quickSort(vector<int> &a,int low,int high)
{
    if(low >= high) return ;
    int pivot = partition(a,low,high);
    quickSort(a,low,pivot-1);
    quickSort(a,pivot+1,high);
}

int main()
{
    vector<int> a = {1,40,10,4,5,8,9,1};
//    cout << partition2(a,0,a.size()-1) << "|";
    quickSort(a,0,a.size()-1);
    for(auto c : a) cout << c << " ";
    return 0;
}

  • 思路引擎

  • 快速排序是利用分治策略完成,分成三部曲:分解,解决,合并
    问题定义:将序列 A [ 1.. n ] A[1..n] 非降序
    分解:将序列 A [ l . . h ] A[l..h] 找到r,并使得 A [ l . . r 1 ] A[l..r-1] 的元素均小于 A [ r ] A[r] A [ r + 1.. h ] A[r+1..h] 均大于 A [ r ] A[r]
    解决:递归的调用快速排序算法,对 A [ l . . r 1 ] A[l..r-1] A [ r + 1.. h ] A[r+1..h] 在进行排序
    合并:所有子数组已经有序,无需进行合并操作。

  • 如何分解子数组,使得满足分治的目的
    方法一:

    1. 空出首元素(首元素作为主元), h h l l 端向序列中间进行扫描;
    2. 对于 h h 端元素,如果该元素小于 A [ r ] A[r] 填补 l l 端空缺位,那么该元素所占据的位置就成了空缺位,然后切换到 l l 端;
    3. 对于 l l 端元素,如果该元素大于 A [ r ] A[r] 填补 l l 端空缺位,那么该元素所占据的位置就成了空缺位,然后切换到 h h 端;
    4. l l 端与 l l 端相遇,那么相遇点一定是空缺位,再用主元元素填补就可以了。

    方法二:

    1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
    2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
    3. 从j开始向前搜索,即由后开始向前搜索(j- -),找到第一个小于key的值A[j],将A[j]和A[i]互换;3. 从j开始向前搜索,即由后开始向前搜索(j- -),找到第一个小于key的值A[j],将A[j]和A[i]互换;
    4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
    5. 重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

    方法二的描述引用自 百度百科-快速排序

  • 时间复杂度

    1. 快速排序的时间复杂度与数组划分的效果有关,下面给出两个极端下的时间复杂度求解

      • 当每次数组划分都只纯粹划分成长度为1和长度为 h l h-l 的两个部分,时间复杂度
        T ( n ) = T ( n 1 ) + o ( n ) T ( n ) = o ( n 2 ) T(n) = T(n-1) + o(n) \\ \Rightarrow T(n) = o(n^2)
      • 当每次数组划分是都能划分成两个相等的部分,则时间复杂度可表示
        T ( n ) = 2 T ( n 1 ) + o ( n ) T ( n ) = o ( n l o g n ) T(n) = 2T(n-1) + o(n) \\ \Rightarrow T(n) = o(nlogn)
        这里由递归式求时间复杂度采用的主方法,感兴趣的可移步。
    2. 由于上面的分析可以看出数据的分布对快排的影响很大,为了尽量减少这方面的影响,我们在选取主元的时候尽量随机选择,而不是机械的选择最首或者最尾。这样快排的期望时间复杂度就变成了 o ( n l g n ) o(nlgn) ,具体的证明可以参考算法导论的相关内容。

  • 稳定性分析
    这边很容易可以看出快排是不稳定,原因和选择排序比较类似,我们在进行置换元素时,无法判定置换后的位置前方是否存在键值相同元素,换句话说在置换时无法确保相同键值元素的相对次序。

猜你喜欢

转载自blog.csdn.net/lishang6257/article/details/83037445