快速排序

一、快速排序简述
1、快速排序是对冒泡排序的一种改进;
2、快速排序是由C.A.RHoare于1962年提出的;
3、快速排序的基本思想
(1)先找到一个基准值;
(2)将所有比基准值小的数放在基准值的左边区域,所有比基准值大的数放在基准值的右边区域,等于基准值的数可放在左右任一区域;
(3)对左右区域按照上述方法分别进行排序,整个快速排序可以递归进行,直到数据有序为止。
二、三种快排算法
1、左右指针法
(1)算法描
第一步:定义两个指针left和right分别指向数组的第一个 元素和最后一个元素,并且定义最后一个元素为基准值K;

第二步:left从左向右走,寻找比K大的数A
right从右向左走,寻找比K小的数B
当A和B不相等时将A、B进行交换
然后left继续向右走,right继续向左走

第三步:当left等于right时,将left指向的数赋给K
再将left返回
(2)举例说明
这里写图片描述
(3)代码实现

//左右指针法
int PartSort1(int* a, int left, int right)
{
    assert(a);
    int key = a[right];
    while (left < right)
    {
        //left指针找比key大的数
        while (left < right && a[left] <= key)
        {
            ++left;
        }
        //right指针找比key小的数
        while (left<right && a[right]>=key)
        {
            --right;
        }
        //当left指向数跟right指向的数不相同时进行交换
        if (a[left] != a[right])
        {
            swap(a[left], a[right]);
        }
    }
    //当left等于right时,将left指向的值和标准值进行交换
    swap(a[left], key);
    return left;
}

2、挖坑法
(1)算法描述
第一步:定义两个指针left和right分别指向待排序数组的第一个元素和最后一个元素,并将待排序数组中的最后一个元素key所在的位置作为“坑”;

第二步:left指针向右走,寻找比key大的值,找到之后,将该数据填入坑中,并将此时left所指的位置作为坑;

第三步:right向左走,寻找比key小的值,找到之后将该数据填入坑中,并将此时right所指向的位置作为坑;

第四步:当left等于right时,将数据key填入坑中,至此第一趟排序结束;

第五步:对左右区间进行递归排序,直到待排序数组有序为止。
(2)举例说明
这里写图片描述
(3)代码实现

//挖坑法
int PartSort2(int* a, int left, int right)
{
    assert(a);
    int key = a[right];
    int pit = right;

    while (left < right)
    {
        //指针left向右找比key大的数
        while (left < right && a[left] <= key)
        {
            ++left;
        }
        //找到之后填坑,并制造新坑
            a[pit] = a[left];
            pit = left;
        //指针right向左找比key小的数
        while (left < right && a[right] >=key)
        {
            --right;
        }
        //找到之后填坑,并制造新坑
            a[pit] = a[right];
            pit = right;
    }
    a[pit] = key;
    return left;
}

3、前后指针法
(1)算法描述
第一步:定义两个指针prev和cur分别指向数组的最左端和最左端的前一个位置,并定义数组最右端的值为key值;

第二步:cur向右走,寻找小于key的值,找到了之后与++prev位置上的值进行交换;如果没有找到小于key的值,则一直向右走,直到cur=right时,将a[cur]与a[++prev]进行交换;

第三步:递归进行,直到数组有序为止。
(2)举例说明
这里写图片描述
(3)代码实现

//前后指针法
int PartSort3(int* a, int left, int right)
{
    int key = a[right];
    int cur = left;
    int prev = cur - 1;

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

三、快速排序的性能分析
1、时间复杂度
最优情况:如果对8个无序的数进行快排,那么需要递归3次,递归的每一次都要处理n个数,因此此时递归的时间复杂度为O(n*logn);
最差情况:如果对8个顺序或者逆序的数进行快排,会得到一个只比上次划分少一个的子序列,因此需要递归n-1次,所以此时的时间复杂度为O(n^2);
综上所述,平均情况下,快排的时间复杂度为O(n*logn)
2、空间复杂度
最好情况:需要递归logn次,因此空间复杂度为O(logn);
最坏情况:需要递归n-1次,因此时间复杂度为O(n);
综上所述,平均情况下,快排的时间复杂度为0(logn)
3、稳定性
由于在快排的过程中,关键字的比较和交换是跳跃进行的,因此快排不稳定。
四、快排的优化
1、三数取中法
(1)问题引入
经过上文的描述,我们知道快速排序的效率还是比较高的,但是当我们选择的基准数接近最大or最小,或者待排序数列是顺序或者逆序的时候,效率就比较低了,为了解决这一问题,我们在选择基准数的时候可以采取三数取中法。
(2)算法描述
选取该组数中的第一个数、中间一个数、和最后一个数,选择这三个数中居中的一个数作为基准值。
(3)代码实现

//三数取中
int GetMidIndex(int *a, int left, int right)
{
    int mid = left -((left - right) >> 1);
    if (a[left] < a[mid])
    {
        if (a[mid] < a[right])
        {
            return mid;
        }
        else if (a[left] < a[right])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
    else //(a[left] >= a[mid])
    {
        if (a[mid] > a[right])
        {
            return mid;
        }
        else if (a[right] < a[left])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
}

2、小区间优化
(1)问题引入
当待排序元素较少时,直接插入排序比快排的效率要高,因此在这种情况下采用直接插入排序。
(2)直接插入排序图示(以升序为例)
这里写图片描述
(3)代码实现

void InsertSort(int *a, size_t n)
{
    assert(a);
    for (int i = 1; i < n; ++i)
    {
        int tmp = a[i];
        int j = i - 1;
        for (; j >= 0; --j)
        {
            if (a[j]>tmp)
            {
                a[j + 1] = a[j];
            }
            else
                break;
        }
        a[j + 1] = tmp;
    }

3、非递归实现快排
(1)问题引入
当待排序的数据较多时递归的次数也随增多,会不停的创建栈帧,因此我们借助栈用非递归来实现快排。
(2)代码实现

//非递归实现快排
void QuickSortNOR(int *a, int left, int right)
{
    assert(a);
    stack<int> s;
    s.push(right);
    s.push(left);

    while (!s.empty())
    {
        int start = s.top();
        s.pop();
        int end = s.top();
        s.pop();

        int div = PartSort3(a, start, end);
        if (start < div - 1)
        {
            s.push(div - 1);
            s.push(left);
        }
        if (end > div + 1)
        {
            s.push(end);
            s.push(div + 1);
        }
    }
}

下面给出快排的完整代码

#include <algorithm>
#include <vector>
#include <stack>
#include <assert.h>
#include <iostream>
using namespace std;
//三数取中
int GetMidIndex(int *a, int left, int right)
{
    int mid = left -((left - right) >> 1);
    if (a[left] < a[mid])
    {
        if (a[mid] < a[right])
        {
            return mid;
        }
        else if (a[left] < a[right])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
    else //(a[left] >= a[mid])
    {
        if (a[mid] > a[right])
        {
            return mid;
        }
        else if (a[right] < a[left])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
}

//左右指针法
int PartSort1(int* a, int left, int right)
{
    assert(a);
    int key = GetMidIndex(a, left, right);
    while (left < right)
    {
        //left指针找比key大的数
        while (left < right && a[left] <a[key])
        {
            ++left;
        }
        //right指针找比key小的数
        while (left<right && a[right]>a[key])
        {
            --right;
        }
        //当left指向数跟right指向的数不相同时进行交换
        if (a[left] != a[right])
        {
            swap(a[left], a[right]);
        }
    }
    //当left等于right时,将left指向的值和标准值进行交换
    swap(a[left], a[key]);
    return left;
}

//挖坑法
int PartSort2(int* a, int left, int right)
{
    assert(a);
    int key = a[right];
    int pit = right;

    while (left < right)
    {
        //指针left向右找比key大的数
        while (left < right && a[left] <= key)
        {
            ++left;
        }
        //找到之后填坑,并制造新坑
            a[pit] = a[left];
            pit = left;
        //指针right向左找比key小的数
        while (left < right && a[right] >=key)
        {
            --right;
        }
        //找到之后填坑,并制造新坑
            a[pit] = a[right];
            pit = right;
    }
    a[pit] = key;
    return left;
}

//前后指针法
int PartSort3(int* a, int left, int right)
{
    int key = a[right];
    int cur = left;
    int prev = cur - 1;

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

//递归实现快排
void QuickSort(int* a, int left, int right)
{
    assert(a);
    if (left < right)
    {
        int div = PartSort3(a, left, right);
        QuickSort(a, left, div - 1);
        QuickSort(a, div+1, right);
    }
}

//非递归实现快排
void QuickSortNOR(int *a, int left, int right)
{
    assert(a);
    stack<int> s;
    s.push(right);
    s.push(left);

    while (!s.empty())
    {
        int start = s.top();
        s.pop();
        int end = s.top();
        s.pop();

        int div = PartSort3(a, start, end);
        if (start < div - 1)
        {
            s.push(div - 1);
            s.push(left);
        }
        if (end > div + 1)
        {
            s.push(end);
            s.push(div + 1);
        }
    }
}

测试代码

#include "Sort.h"
#include <assert.h>
#include <iostream>
using namespace std;
int main()
{
    int a[] = { 7, 9, 8, 4, 6, 1, 2, 3,7,10,11,5,20,21,27,13,19,16,12 };
    size_t size = sizeof(a) / sizeof(a[0]);
    QuickSortNOR(a, 0, size - 1);
    PrintArr(a, size);
    return 0;
}

测试结果
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值