快速排序算法_图文详解_这一篇就够了!

快速排序

一、原理

快速排序是比较快的排序方法。 它的基本思想是通过一组排序将要排序的数据分割成独立的两部分,首先数组第一个元素作为基值(首元素可能不会是最优基值,那么怎样选取基准元素呢?),其中一部分的所有数据大于基值,另外一部分的所有数据小于等于基值,然后再按此方法对这两部分数据进行快速排序,整个排序过程可以递归进行,以此使所有数据变成有序序列。

二、算法设计

快速排序算法的思想如下:

  • 分解:先从原序列中取出一个元素作为基值。以基值元素为界,将原序列分解为两个子序列,小于或等于基值元素的子序列在基值左侧,大于或等于基值元素的子序列在基值右侧;
  • 治理:对两个子序列进行快速排序
  • 合并:将两个有序子序列合并为一个有序序列,得到原问题的解

三、分治策略

如何分解是一个难题,因为若基准元素选取不当,原序列就有可能被分解为规模为 0 和 n-1 的两个子序列,这样快速排序就退化为冒泡排序了。

例如,有序列(30,24,5,58,18,36,12,42,39),采用快速排序的分治策略,第 1 次选取 5 作为基准元素,序列被分解后如下图所示。

第 2 次选取 12 作为基准元素,序列被分解后如下图所示:

这样做的效率是最低的,最理想的状态是将原序列分解为两个规模相当的子序列,那么怎样选取基准元素呢?一般来说,可通过以下方法选取基准元素:

  • 选取第一个元素;

  • 选取最后一个元素;

  • 选取中间位置的元素;

  • 选取第一个元素、最后一个元素、中间位置的元素这三者的中位数;

  • 选取区间位置随机数 k(left≤k≤right),选取 a[k] 作为基准元素。

    四、快速排序图解

    再此,先拿数组首元素作为基值。

    设当前待排序序列为 a[left:right],其中 left≤right:

  • 选取数组的第一个元素作为基准元素,pivot=a[left],i=left,j=right;

  • 从右向左扫描,找小于或等于 pivot 的数,令 a[i]=a[j],i++;

  • 从左向右扫描,找大于或等于 pivot 的数,令 a[j]=a[i],j--;

  • 重复第 2~3 步,直到 i 和 j 重合,将 pivot 放到中间,即 a[i]=pivot,返回 mid=i。

此时完成一趟排序,分为以mid为界的两个子序列,左侧子序列都小于或等于pivot,右侧子序列都大于pivot。然后合并两个序列,进行快速排序。

下面序列(30, 24, 5, 58, 18, 36, 12, 42, 39)为例,演示快速排序的过程。

  1. 初始化。i=left,j=right,pivot=r[left]=30。

2) 从右向左扫描,找小于或等于 pivot 的数,找到 a[j]=12。

swap(a[i],a[j]),i++,如下图所示:

3) 向右走,从数组的左边位置向右找,一直找到比 pivot 大的数,找到 R[i]=58 ,R[i] 与 R[j] 交换 ,j--。

4) 向左走,从数组的右边位置向左找,一直找到小于等于 pivot 的数,找到R[j]=18,R[i]与R[j]交换,i++。如下图所示:

5) 向右走,从数组的左边位置向右找,一直找到比 pivot 大的数,这是 i=j,第一轮排序结束,返回 i 的位置,mid=i 。以上的操作是对序列进行分解,其代码如下图所示:

cpp 复制代码
   #include <iostream>
   #include <algorithm>

//划分函数
int part(int* n, int low, int hight)
{
    //基值
    int i = low;
    int j = hight;
    int pivot = n[low];
    while (i != j)
    {
        while (i < j && n[j] > pivot){ j--; }//从右向左开始找一个 小于等于 pivot的数值
        if (i < j)
        {
            std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
        }
        while (i < j && n[i] <= pivot) { i++; }//从左向右开始找一个 大于 pivot的数值
        if (i < j)
        {
            std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位
        }
    }
    return i;//返回最终划分完成后基准元素所在的位置
}

6)然后在分别对这两个序列(12,24,5,18)和(36,58,42,39)进行快速排序(递归)。其代码如下所示:

cpp 复制代码
//合并排序
void QuickSort(int* n, int low, int hight)
{
    if (low < hight)
    {
        int mid = part(n, low, hight); // 返回基准元素位置
        QuickSort(n, low, mid - 1); // 左区间递归快速排序
        QuickSort(n, mid + 1, hight); // 右区间递归快速排序
    }

}

五、AC代码

cpp 复制代码
#include <iostream>
#include <algorithm>

//划分函数
int part(int* n, int low, int hight)
{
    //基值
    int i = low;
    int j = hight;
    int pivot = n[low];
    while (i != j)
    {
        while (i < j && n[j] > pivot) { j--; }//从右向左开始找一个 小于等于 pivot的数值
        if (i < j)
        {
            std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
        }
        while (i < j && n[i] < pivot) { i++; }//从左向右开始找一个 大于 pivot的数值
        if (i < j)
        {
            std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位
        }
    }
    return i;//返回最终划分完成后基准元素所在的位置
}


//合并排序
void QuickSort(int* n, int low, int hight)
{
    if (low < hight)
    {
        int mid = part(n, low, hight); // 返回基准元素位置
        QuickSort(n, low, mid - 1); // 左区间递归快速排序
        QuickSort(n, mid + 1, hight); // 右区间递归快速排序
    }

}

int main()
{
    int n[100];
    int count;
    std::cout << "请输入要排序的数据的个数: " << std::endl;
    std::cin >> count;

    std::cout << "请输入要排序的数据: " << std::endl;
    for (int i = 0; i < count; i++)
    {
        std::cin >> n[i];
    }
    std::cout << std::endl;
    QuickSort(n, 0, count-1);
    std::cout << "排序后的数组为:" << std::endl;
    for (int i = 0; i < count; i++)
    {
        std::cout << n[i] << " ";
    }
    std::cout << "\n";
    return 0;
}
  • 输出结果

六、快速排序优化拓展(参考)

为避免出现最坏情况 (分解:划分函数需要扫描每个元素,每次扫描的元素数量都不超过 n,因此时间复杂度为 O(n)。) ,可以在选取基准元素时引入随机化策略,首先生成一个 [left,right] 区间的随机数 k,然后将 a[k] 和 a[left] 交换,其他代码保持不变。

算法代码:

cpp 复制代码
int part(int* n, int low, int hight) 
{ // 划分函数,引入随机化策略
    int k = low + rand() % (hight - low + 1); // 生成 [left, right] 区间的随机数
    std::swap(n[k], n[low]);
    int i = low, j = hight, pivot = n[low];
    while (i < j) {
        while (n[j] > pivot && i < j) { j--; } // 找右侧小于或等于 pivot 的数
        if (i < j)
        {
            std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
        }
        while (n[i] < pivot && i < j) { i++; } // 找左侧大于或等于 pivot 的数
        if (i < j)
            std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位    
    }
        n[i] = pivot; // 放到中间
        return i;
}

注意:

  • 选取基值时应尽量引入随机策略。选取第一个元素或最后有个元素为基值时,若序列本身有序,则会退化为最坏情况,出现超时情况;
  • 在划分函数中,若引入随机策略,与pivot比较的语句不要带等号,例如while(n[i] <= pivot && i < j),当序列元素均等时,则会退化为最坏情况,出现超时的情况。

七、共勉

txt 复制代码
以上是我对快速排序的理解,如果有误,烦请评论指出,一起学习,无限进步!!! 
相关推荐
逍遥德1 天前
Java8 Comparator接口 和 List Steam 排序使用案例
java·spring boot·list·排序算法
中华小当家呐2 天前
算法之常见八大排序
数据结构·算法·排序算法
kk”2 天前
C语言快速排序
数据结构·算法·排序算法
papership2 天前
【入门级-算法-6、排序算法:选择排序】
数据结构·算法·排序算法
AI 嗯啦3 天前
计算机的排序方法
数据结构·算法·排序算法
小欣加油3 天前
leetcode 912 排序数组(归并排序)
数据结构·c++·算法·leetcode·排序算法
NMZH103 天前
排序算法(全--C语言)
算法·排序算法
對玛祷至昏3 天前
算法学习路径
学习·算法·排序算法
梁辰兴4 天前
数据结构:排序
数据结构·算法·排序算法·c·插入排序·排序·交换排序
楼田莉子4 天前
C++算法专题学习——分治
数据结构·c++·学习·算法·leetcode·排序算法