快速排序的三种实现方式以及非递归版本

一、快速排序的基本思想

快速排序利用了分治的思想,分而治之。通过一趟排序将序列分为两部分,其中一部分比较关键字小,一部分比关键字大。之后继续对这两个子序列重复此过程,直到整个序列都有序。

二、快速排序的三个步骤

  1. 选基准值:在要排序的序列中选一个几个基准值,作为划分区间的 关键字。通常选取关键字的方式对效率有很大的影响。
  2. 分割序列:按照基准值把序列分为两部分,左边的小于基准值,右边的大于基准值。
  3. 重复分割:对子序列重复上面的步骤,知道序列为空或者只有一个元素。当递归处理的所有子序列都返回时,序列有序。

三、三种实现方式

注:下面代码中所有区间都是左闭右开,[ left, right),且排序都是从大到小。且基准值默认选择序列的最后一个元素

一、版本一填充法

思路:两个指针 begng作为序列的开始 和 end 序列的结束。

将序列的最后一个元素保存起来,作为基准值。

开始划分序列:直到begin < end 不成立

begin 从左往右找比 基准值 大的元素,将该值填充到 end 的位置(end 位置上的值已经保存到 key),end-- ,因为已经划分好了一个元素。
end 从右往左找比 基准值小的元素,如将该值填充到 begin 的位置(begin 位置上的值已经保存到 end位置),begin++,已经划分好一个元素。

循环结束时,将 key 填充到 end 的位置,然后将 end 返回,作为下一次划分序列的边界。

// 版本一 填充法
int partition(vector<int> &v, int left, int right)
{
    int begin = left;
    int end = right - 1;
    int key = v[end];          // 选基准

    while (begin < end)
    {
        while (begin < end && v[begin] <= key)
            begin++;
        if (begin < end)
            v[end--] = v[begin];
        while (begin < end && v[end] >= key)
            end--;
        if (begin < end)
            v[begin++] = v[end];
    }
    v[end] = key;
    return end;
}
void QuickSort(vector<int>& v, int left, int right)
{
    if (left < right)
    {
        int boundary = partition(v, left, right);
        QuickSort(v, left, boundary);
        QuickSort(v, boundary + 1, right);
    }
}

二、版本二 交换法

和上面的版本一类似,不同之处是,这里不是填充,而是将找到的元素交换。

// horn版本
int partition(vector<int> &v, int left, int right)
{
    int begin = left;
    int end = right - 1;
    int key = v[end];
    while (begin < end)
    {
        while (begin < end && v[begin] <= key)
            begin++;
        while (begin < end && v[end] >= key)
            end--;

        if (begin < end)
            std::swap(v[begin], v[end]);
    }
    if(end != right -1)
     std::swap(v[end], v[right-1];)

    return end;
}
void QuickSort(vector<int>& v, int left, int right)
{
    if (left < right)
    {
        int boundary = partition(v, left, right);
        QuickSort(v, left, boundary);
        QuickSort(v, boundary + 1, right);
    }
}

我认为一,二的主要不同在于一个是填充一个是交换,当比较对象较大时,填充的效率是高于交换,不需要在构造一个临时变量。

三、版本三 双指针前移法

该方法短小精悍。

思路:
两个指针从同一个方向走。
cur 指向当前元素,每次循环都要往前走,直到遍历完序列,所以循环结束的条件就是 cur < right ,right 为 序列的右边界。

prev 指向cur的前一个位置。

如果cur 指向的值小于 基准值, 那么就让 prev前进一个位置,并判断 cur 是否等于 prev,如果不等,说明它两不在同一位置,那么交换位置上的值。

cur 要么相邻,要么中间间隔的都是别基准值大的元素。cur 的目的就是替 prev 清理前进路上的障碍—“比基准值小的元素”。剩下的都是比基准值大的元素, 只要当 cur 找到比基准值小的,就放心的交换。

循环结束时,需要将基准值,即序列的最后一个元素,与 prev下一个位置上的元素 交换。因为这个prev 所在的位置是比基准值小的元素,那么prev 的下一个位置一定比 prev 大,将这个比基准值大的元素与基准值交换就完成了一次划分。

int partition1(vector<int> &v, int left, int right)
{
    int prev = left - 1;
    int cur = left;
    int key = v[right - 1];

    while (cur < right)
    {
        if (v[cur] < key && ++prev != cur)
            std::swap(v[cur], v[prev]);
        cur++;
    }
    
    if (v[++prev] != key)
        std::swap(v[prev], v[right - 1]);
    return prev;
}


void QuickSort(vector<int>& v, int left, int right)
{
    if (left < right)
    {
        int boundary = partition1(v, left, right);
        QuickSort(v, left, boundary);
        QuickSort(v, boundary + 1, right);
    }
}

非递归版本

每次用到递归,大家都会担心递归太深是否会栈溢出?我测试了900000000个int 整型的排序(3.35GB左右内存),栈也木有溢出啊。。

尽管如此还是给出非递归的实现版本吧,比较栈资源宝贵。

利用栈将序列起始位置与结束位置保存起来。


void QuickSortNor(vector<int> &v, int left, int right)
{
    if (left >= right)
        return;
    stack<int> s;
    s.push(left);
    s.push(right);

    while (!s.empty())
    {
        int right = s.top();
        s.pop();
        int left = s.top();
        s.pop();
        if (left < right)
        {
            int boundary = partition1(v, left, right);
            // 左区间
            s.push(left);
            s.push(boundary);
            // 右区间
            s.push(boundary + 1);
            s.push(right);
        }

    }
}

简单总结一下:

快速排序,是一种不稳定的排序,时间复杂度平均为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),逆序序列下效果最差,逼近 O ( n 2 ) O( n^2) O(n2),其空间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n),因为需要空间来保存递归的区间范围。


2019年03月05日11:43:34 增加测试双指针法代码

#include <iostream>
#include <cstdlib>
#include <vector>
#include <ctime>
#include <algorithm>
#include <functional>
using namespace std;

int partition1(vector<int> &v, int left, int right)
{
    int prev = left - 1;
    int cur = left;
    int key = v[right - 1];

    while (cur < right)
    {
        if (v[cur] < key && ++prev != cur)
            std::swap(v[cur], v[prev]);
        cur++;
    }

    if (v[++prev] != key)
        std::swap(v[prev], v[right - 1]);
    return prev;
}


void QuickSort(vector<int>& v, int left, int right)
{
    if (left < right)
    {
        int boundary = partition1(v, left, right);
        QuickSort(v, left, boundary);
        QuickSort(v, boundary + 1, right);
    }
}


int main()
{
    // 生成随机数, 使用自己写的排序和系统默认排序, 在排序前后比较vector 检测是否有效
    std::vector<int> test;
    std::vector<int> ok;
    int test_num = 100000;
    for(int i = 0; i < test_num; i++)
    {
        int a = std::rand()/1000000;
        test.push_back(a);
        ok.push_back(a);
    }
    if (test == ok )
    {
        std::cout << "sort before eq" << std::endl;
    }
    QuickSort(test, 0, test.size());
    // 测试数据较大时, 注释掉了输出每一个
    for (int i = 0; i < test.size(); i++)
    {
        //std::cout << test[i] << " ";
    }
    std::cout << endl;
    std::sort(ok.begin(), ok.end());
    for (int i = 0; i < ok.size(); i++)
    {
        //std::cout << ok[i] << " ";
    }
    std::cout << endl;

    if (test == ok )
    {
        std::cout << "sort after eq" << std::endl;
    }
    return 0;
}

输出:

sort before eq


sort after eq
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页