首页 > 编程语言 >快速排序算法

快速排序算法

时间:2023-09-19 20:26:14浏览次数:53  
标签:int 元素 主元 算法 排序 快速 Left

快速排序

1. 快速排序的思想

快速排序是一种分治的排序算法,是对于冒泡排序的改进算法,在C语言标准库中的函数qsort()的实现就是快速排序。(下述快速排序都是最后要求值按从小到大排序)

快速排序的核心思想在于:
每次都选择主元,然后利用主元进行划分,使得左边的元素都小于主元,右边的元素都大于主元,然后对左右两边的元素进行相同原理的递归排序。

快速排序的难点在于:

  1. 如何选择主元
  2. 如何进行划分
  3. 如何进行递归排序
  4. 边界处理

2. 快速排序的实现

2.1 《算法导论》中的实现

普通的快速排序:

PARTITION(A, p, r)  // 划分,p为数组的第一个元素,r为数组的最后一个元素
    x = A[r]        // 选取最后一个元素作为主元
    i = p - 1       // i为指针,指向第一个元素的前一个元素
    for j = p to r - 1  // j为指针,指向第一个元素,到倒数第二个元素,因为最后一个元素是主元
        if A[j] <= x    // 如果j当前元素小于主元
            i = i + 1   // i指针向后移动一位
            exchange A[i] with A[j] // 交换A[i]与A[j]
    exchange A[i + 1] with A[r]     // 
    return i + 1

QUICKSORT(A, p, r)  // p为数组的第一个元素,r为数组的最后一个元素
    if p < r        // 当划分到只有一个元素的时候,不需要进行排序
        q = PARTITION(A, p, r)  // 划分,q为主元的位置
        QUICKSORT(A, p, q - 1)  // 对左边的元素进行排序
        QUICKSORT(A, q + 1, r)  // 对右边的元素进行排序

上面的这段注释是我自己加

随机化的快速排序:

RANDOMIZED-PARTITION(A, p, r)
    i = RANDOM(p, r)            // 随机选取一个元素
    exchange A[r] with A[i]     // 交换A[r]与A[i],这样就可以将随机选取的元素作为主元
    return PARTITION(A, p, r)   // PARTITION是上面的划分函数

RANDOMIZED-QUICKSORT(A, p, r)
    if p < r
        q = RANDOMIZED-PARTITION(A, p, r)
        RANDOMIZED-QUICKSORT(A, p, q - 1)
        RANDOMIZED-QUICKSORT(A, q + 1, r)

2.2 陈越老师的《数据结构(第2版)》中的实现

ElementType Median3(ElementType A[], int Left, int Right)
{
    int Center = (Left + Right) / 2;
    if (A[Left] > A[Center])
        Swap(&A[Left], &A[Center]);
    if (A[Left] > A[Right])
        Swap(&A[Left], &A[Right]);
    if (A[Center] > A[Right])
        Swap(&A[Center], &A[Right]);
    /* 此时A[Left] <= A[Center] <= A[Right] */
    Swap(&A[Center], &A[Right - 1]);    /* 将基准Pivot藏到右边*/
    return A[Right - 1];         /* 返回基准Pivot */
}

void Qsort(ElementType A[], int Left, int Right)
{
    /* 核心递归函数 */
    int Pivot, Cutoff, Low, High;

    if (Cutoff <= Right - Left) /* 如果序列元素充分多,进入快排 */
    {
        Pivot = Median3(A, Left, Right);    /* 选基准 */
        Low = Left;
        High = Right - 1;
        while (1)   /*将序列中比基准小的移到基准左边,大的移到右边*/
        {
            while (A[++Low] < Pivot)
                ;
            while (A[--High] > Pivot)
                ;
            if (Low < High)
                Swap(&A[Low], &A[High]);
            else
                break;
        }
        Swap(&A[Low], &A[Right - 1]);   /* 将基准换到正确的位置 */
        Qsort(A, Left, Low - 1);    /* 递归解决左边 */
        Qsort(A, Low + 1, Right);   /* 递归解决右边 */
    }
    else
        InsertionSort(A + Left, Right - Left + 1);  /* 元素太少,用简单排序 */
}

void QuickSort(ElementType A[], int N)
{
    /* 统一接口 */
    Qsort(A, 0, N - 1);
}

2.3 严蔚敏老师的《数据结构(C语言版)》中的实现

int Partition(SqList &L, int low, int high)
{
    // 交换顺序表L中子表r[low..high]的记录,枢轴记录到位,并返回其所在位置
    // 此时在它之前(后)的记录均不大(小)于它
    L.r[0] = L.r[low];              // 用子表的第一个记录作枢轴记录
    int pivotkey = L.r[low].key;    // 枢轴记录关键字
    while (low < high)              // 从表的两端交替地向中间扫描
    {
        while (low < high && L.r[high].key >= pivotkey)
            --high;
        L.r[low] = L.r[high];       // 将比枢轴记录小的记录移到低端
        while (low < high && L.r[low].key <= pivotkey)
            ++low;
        L.r[high] = L.r[low];       // 将比枢轴记录大的记录移到高端
    }
    L.r[low] = L.r[0];              // 枢轴记录到位
    return low;                     // 返回枢轴位置
}

void QSort(SqList &L, int low, int high)
{
    // 对顺序表L中的子序列L.r[low..high]作快速排序
    if (low < high)                             // 长度大于1
    {
        int pivotloc = Partition(L, low, high); // 将L.r[low..high]一分为二,算出枢轴值pivotloc
        QSort(L, low, pivotloc - 1);            // 对低子表递归排序, pivotloc是枢轴位置
        QSort(L, pivotloc + 1, high);           // 对高子表递归排序
    }
}

void QuickSort(SqList &L)
{
    // 对顺序表L作快速排序
    QSort(L, 1, L.length);
}

2.4 个人常用的模版

void ExChange(int *a, int *b) // 交换两个数
{
    int t = *a;
    *a = *b;
    *b = t;
}

int Partition(int *a, int l, int h) // 划分函数
{
       int i = l - 1, j = h + 1;    // i, j 为左右指针, 由于后面使用do while循环,首先会对i,j进行运算,所以i, j的初始值要分别为l - 1, h + 1
       int x = a[(l + h) / 2];     // 选取基准数,这里选取中间数,也可以随机选取,用来应对加强数据
       
       while(i < j)
       {
            do i++; while(a[i] < x);    // 从左往右找到第一个大于等于基准数的数
            do j--; while(a[j] > x);    // 从右往左找到第一个小于等于基准数的数
            if(i < j) ExChange(&a[i], &a[j]);   // 交换两个数, 这个位置可以直接使用swap函数
       }
    return j; 
}

void QuickSort(int *a, int l, int h) // 快速排序
{
    if(l >= h) return;  // 递归边界
    int q = Partition(a, l, h); // 划分
    QuickSort(a, l, q); // 递归左边
    QuickSort(a, q + 1, h); // 递归右边
}

个人推荐上述代码,比较好记,但是可能难理解

3. 快速排序代码上的分析

有关于代码细节的分析,推荐这篇文章 《AcWing 785. 快速排序算法的证明与边界分析》,我个人记得快速排序的代码模版,就是来自这篇文章。

下面我关于我在学习快速排序的过程中,对于快速排序的一些理解。

在《算法导论》、陈越老师的《数据结构(第2版)》中,选取了最后一个元素作为主元。在严蔚敏老师的《数据结构(C语言版)》、《算法图解》、《算法(第4版)》中,选取的是第一个元素作为主元。因此,主元的选择是可以变化的,但是需要注意的是,在某些数据里,主元的选择直接影响了最后的程序的处理时间。比如785. 快速排序 - AcWing题库 里的数据就不能简单选取第一个元素作为主元,否则会超时。个人认为,主元的选择应该随机。

就我已给出的几组代码而言,在《算法导论》、陈越老师的《数据结构(第2版)》和严蔚敏老师的《数据结构(C语言版)中都是选取的一个主元,并在排序完成后主元不变。但是在 2.4 个人常用的模版 里给出的代码里选取的主元在本轮排序的最后可能出现主元变换的情况,但是这并不影响排序,因为主元划分数组是在本轮排序后进行,我们只需要保证最后我们挑出来的主元符合要求(即划分后,主元的左边都是小于主元的,主元的右边都是大于主元的)即可。

下面我摘抄一段《算法导论》中对于快速排序的完整描述:

快速排序的三步分治过程:
分解:数组 \(A[p...r]\) 被划分为两个(可能为空)的子数组 \(A[p...q-1]\) 和 \(A[q+1...r]\) ,使得 \(A[p...q-1]\) 中的每一个元素都小于等于 \(A[q]\) ,而 \(A[q]\) 也小于等于 \(A[q+1...r]\) 中的每个元素。其中,计算下标q也是划分过程的一部分。
解决:通过递归调用快速排序,对子数组 \(A[p...q-1]\) 和 \(A[q+1...r]\) 进行排序。
合并:因为子数组都是原址排序的,所以不需要合并操作:数组 \(A[p...r]\) 已经有序。

可以注意的是《算法导论》并没有规定主元的选取是固定的,或者有次序的。在我刚接触快速排序的时候认为,主元的选取应该是固定的,并且应该先选主元然后再进行排序。但是后来发现其实我们整个过程只需要保证最后主元的位置是正确的,而不需要保证主元的位置是固定的。因此,我们可以在排序的过程中,随机选取主元,然后将主元放到正确的位置上,这样就可以保证主元的位置是正确的,而不需要保证主元的位置是固定的。在算法实现适当的情况下,可以先进行适当排序,只要返回满足要求的主元的位置即可。比如 2.4 模版 中的代码,就是先进行了一次排序,然后返回了主元的位置,这样就可以保证主元的位置是正确的,而不需要保证主元的位置是固定的。

标签:int,元素,主元,算法,排序,快速,Left
From: https://www.cnblogs.com/BryceAi/p/17715691.html

相关文章

  • 如何将手机上微信的文件快速传递到linux平台?
    要将手机上微信的文件快速传递到Linux平台,你可以尝试以下几种方法:1.通过USB传输:连接手机和Linux计算机,将手机设置为传输文件模式,然后在Linux上使用文件管理器访问手机的存储,从中复制所需的文件到Linux平台。2.使用第三方应用:在手机上安装支持文件传输的第三方应用,如AirDroid、Pus......
  • NFLS-NOIP模拟 排序
    题面Link小Z是一位热爱优化算法的同学。一天他在研究归并排序,并想到,如果在归并排序的过程中提前return,对正确率的影响并不会很大。于是他写了如下部分代码:voidmerge_arr(intl,intmid,intr)//此函数表示将S[1,mid],S[mid+1,r]两个有序序列合并成为一个大的有序序列S[l,r],......
  • sql server单一某列实现排序____附件数据表
    USE[YJ]GO/******Object:Table[dbo].[T_OA_WDSTORE]ScriptDate:04/16/201400:23:38******/SETANSI_NULLSONGOSETQUOTED_IDENTIFIERONGOSETANSI_PADDINGONGOCREATETABLE[dbo].[T_OA_WDSTORE]( [WDBH][nvarchar](50)NOTNULL, [APPBH][nvarc......
  • Lnton羚通机器视觉算法平台人员入侵检测 重点区域人员徘徊算法检测
    Lnton羚通的算法算力云平台是一款出色的解决方案,具备突出的特点。该平台提供高性能、高可靠性、高可扩展性和低成本的功能,使用户能够高效地执行各种复杂的计算任务。此外,平台还提供了丰富的算法库和工具,支持用户上传和部署自定义算法,提高了平台的灵活性和个性化能力。人员闯入识别......
  • DRF之排序类源码分析
    【一】排序类介绍在DjangoRESTframework(DRF)中,排序类用于处理API端点的排序操作,允许客户端请求按特定字段对数据进行升序或降序排序。排序类是一种特殊的过滤类DRF提供了内置的排序类,并且你也可以自定义排序类以满足特定的需求。【二】内置排序类OrderingFilterrest_f......
  • 堆排序
    时间复杂度为O(n)voidheapify(vector<int>&nums,intn,inti){intlargest=i;//假设为父节点intlson=i*2+1;intrson=i*2+2;//找到最大值if(lson<n&&nums[lson]>nums[largest])swap(nums[lson],nums[largest]);if(rson<......
  • 【原创】2-快速记忆秘籍-记忆的另一种分类及规律
    大家好,我又来给大家讲课了,从今天起我会不定期给大家由浅入深讲解下记忆的修炼过程,让大家对自己“肩膀上的圆球”有一个本质的认识。在学习完全部的过程后,你也会发现,原来自己的记忆怎么这么好,是的,其实每个人天生都是最强大脑!l  记忆的另一种分类结合上篇文章,实际上已经把记忆......
  • 【原创】4-快速记忆秘籍-记忆内容的分类
    大家好,我又来给大家讲课了,从今天起我会不定期给大家由浅入深讲解下记忆的修炼过程,让大家对自己“肩膀上的圆球”有一个本质的认识。在学习完全部的过程后,你也会发现,原来自己的记忆怎么这么好,是的,其实每个人天生都是最强大脑!记忆,记忆,到底我们需要记忆什么东西?结合我们的日常经念,......
  • sql server单一某列实现排序
    WDBHAPPBHWDMC430175500443659sg430044033903992转发省环境保护厅省财政厅关于印发广东省排污权有偿使用和交易试点管理办法的通知(会签文)(修改).doc430175500443659430044033903992转发省环境保护厅省财政厅关于印发广东省排污权有偿使用和交易试点管理办法的通知(会签文).doc......
  • 文心一言 VS 讯飞星火 VS chatgpt (96)-- 算法导论9.3 1题
    一、用go语言,在算法SELECT中,输人元素被分为每组5个元素。如果它们被分为每组7个元素,该算法仍然会是线性时间吗?证明:如果分成每组3个元素,SELECT的运行时间不是线性的。文心一言:在算法分析中,一个算法的运行时间是否线性依赖于其输入数据的大小。线性时间意味着算法的运行......