双11狂欢大促

盲盒赢 定制羽绒服

开启惊喜盲盒,抽取你的专属好礼

— 惊喜奖品抢先看 —

【快速排序】:绝对是史上最全的快速排序分析,分析了所有的边界情况并给出算法可视化动图帮助你理解算法

代码kobe 代码kobe 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区

 目录

一:快速排序概念定义

二:题目描述

三:算法详解

do i++; while(q[i] < x);   //用while(q[++i]= x)也是等价的语句>

 

do j--; while(q[j] > x);  会使得 q[j+1..r] >= x, q[j] <= x

if(i < j) swap(q[i], q[j]);  会使得 q[l..i] <= x, q[j..r] >= x

四.常见边界问题汇总以及分析

五.给出以i划分的模板

六.顺便给出从大到小快排的模板(只需要改动两个大小符号即可)


一:快速排序概念定义

        排序问题可以说是最经典的算法问题了,一般一开始学的基础算法就是排序,排序既是算法的基础,也是考研数据结构的重要知识点。但是 C/C++ 都有现成的库函数(C里是 qsort、C++是sort),所以导致很多人就没有学排序算法本身,其实排序算法的思路非常有意思,从最简单的学起,对以后学习复杂算法是非常有帮助的,比如快速排序和快速选择之间的关系,归并排序和逆序对之间的关系。
  从今天开始,我们来试着掌握每个排序算法的思路,今天是快速排序。
  快速排序,采用的是分治的思想,选择一个基准点,把所有小于基准点的数放到它左边,把所有大于基准点的数放到它右边,如图所示:

图示含义
■ 蓝色的柱形表示还未排序的数
■ 黄色的柱形表示随机选定的基准数
■ 橙色的柱形表示已经排好序的数
■ 红色的柱形表示正在遍历比较的数
■ 绿色的柱形表示比基准数小的数
■ 紫色的柱形表示比基准数大的数

 

 

 

 

 

 

 

 

原始数据:

dfb5c79b1dbb4de6907b860224b00f87.png

排序可视化过程:

ae2de55c0521425bbdd66119b2625341.gif

 容易发现:第一次遍历选择的基准数是29,第一遍排序结束的结果是14,10,14,29,37,也就是29左边的数都小于等于29,29右边的数都大于等于29,符合我们对快速排序算法的定义,在此基础上递归排序即可

 


二:题目描述

        给你一个长度为n(n<=100000)的整数数组a,请你将该数组升序排列


三:算法详解

先给出我个人的万能模板(个人习惯以j分界,文章后面详细说明怎么用i分界): 

#include <bits/stdc++.h>
using namespace std;

const int N=100010;

int q[N];

void quicksort (int q[],int l, int r)
{
    if (l>=r) return ;
    int i=l-1,j=r+1,x=q[(l+r)/2];
    while(i<j)
    {
        do i++;while(q[i]<x);
        do j--; while(q[j]>x);
        if(i<j) swap(q[i],q[j]);
    }
    
    quicksort(q,l,j);
    quicksort(q,j+1,r);
}


int main()
{
    int n;
    scanf("%d",&n);
    
    for(int i=0;i<n;i++)
    {
        scanf("%d",&q[i]);
    }
    
    quicksort(q,0,n-1);
    
    for(int  i=0;i<n;i++)
    {
    printf("%d ",q[i]);
    }
}

 

算法证明: 借鉴算法导论的循环不变式的方法

快排核心模板:
1.递归出局
2.处理某个子问题
3.递归处理子问题

//循环体

void quicksort(int q[], int l, int r)
{
    //第一步:规定递归的终止情况
    if(l >= r) return;
    //第二步:分成子问题,注意i和l的初始位置,待会详细说明为什么这么设置i和j和x
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while(i < j)
    {
        do i++; while(q[i] < x);//注意不能有等于,待会详细说明为什么不能有等于
        do j--; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }
    //第三步:以j为分界,划分区间,递归处理子问题
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

证明目标:循环不变式,即任意一次while循环(除了最后一次while循环)结束后,q[l..i] <= x,q[j..r] >= x

证明过程:

1.初始化:i = l - 1, j = r + 1,显然此时q[l..i],q[j..r]为空,证明目标显然成立

2.保持:假设下一轮循环开始之前的循环不变式成立,即 q[l..i] <= x  ,  q[j..r] >= x

执行上述的循环体

do i++; while(q[i] < x);   //用while(q[++i]<x)也是等价的语句
  会使得 q[l..i-1] <= x, q[i] >= x

do j--; while(q[j] > x);
  会使得 q[j+1..r] >= x, q[j] <= x

if(i < j) swap(q[i], q[j]);
  会使得 q[l..i] <= x, q[j..r] >= x

所以,经过循环体之后,循环不变式依然成立 

注:由于i和j的设定问题,i和j必须要先自增!!否则在特殊情况(比如去q[i]和q[j]都等于x)下就会死循环

比如:

 while(q[i] < x)  i++;
  while(q[j] > x)  j--;
当q[i]和q[j]都为 x 时, i 和 j 都不会更新,导致 while 陷入死循环

3.终止:最后一轮循环结束时 j <=i且只有j+1=r或者j=r两种情况


四.常见边界问题汇总以及分析

1.以j划分时,随机值x为什么不能选q[r],以i划分时,随机值x为什么不能选q[l]

证明:假设x取q[r],关键问题在于递归子问题 quick_sort(q, l, j), quick_sort(q, j + 1, r);举例来说,若x选为q[r],且数组中q[l..r-1] < x,那么一轮循环结束时,i = r, j = r,quick_sort(q, l, j)直接退出函数调用,没有排序,以i划分也是同理

2. do i++; while(q[i] < x)和do j--; while(q[j] > x)为什么不能用q[i] <= x 和 q[j] >= x

证明:假设q[l..r]全相等,则执行完do i++; while(q[i] <= x);之后,i会自增到r+1,然后继续执行q[i] <= x 判断条件,造成数组下标越界

3. if(i < j) swap(q[i], q[j])能否使用 i <= j

证明:可以。因为 i = j 时,交换一下q[i],q[j] 无影响,因为马上就会跳出循环了

4.最后一句能否改用左区间quick_sort(q, l, j-1),右区间 quick_sort(q, j, r)作为划分(用i做划分时也有同样的问题) 

证明:不能。

情况一:i=j+1时,显然根据上述循环体的结论同意得到q[l,..j]<=x,q[i,..r](即q[j+1,r])>=x,因此我的原代码的划分一定正确。但是倘若使用上述错误的划分,q[l,..j-1]<=x,没有问题,但是q[j]<=x,却被递归在右半个区间,显然错误

情况二:i=j时,特殊情况i=j=1时,此时很容易得到q[i]=q[j]=x,倘若用上述的错误代码,左边区间调用函数会直接退出,但是右边区间等价于原来的情况原地踏步,所以该划分方式会导致死循环

五.给出以i划分的模板

void quick_sort(int q[], int l, int r)
{
    if(l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r + 1 >> 1];//注意是向上取整,因为向下取整可能使得x取到q[l],这是和j划分不一样的地方
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, i - 1), quick_sort(q, i, r);//不用q[l..i],q[i+1..r]划分的道理和分析4中j的情况一样
}

 

六.顺便给出从大到小快排的模板(只需要改动两个大小符号即可)

void quicksort(int q[], int l, int r)
{
    if(l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while(i < j)
    {
        do i++; while(q[i] > x); // 这里和下面
        do j--; while(q[j] < x); // 这行的判断条件改一下
        if(i < j) swap(q[i], q[j]);
    }
    quicksort(q, l, j), quicksort(q, j + 1, r);
}

如果觉得有问题欢迎私聊,如果绝对写的不错,欢迎支持,创作不易。

七:排序的稳定性以及时空复杂度

 

...全文

相关推荐

D3可视化史上最全中文文档

不解释,想看的就下载吧。可视化神书!

不愿意透露姓名的程序员

算法可视化系列——06快速排序算法——可视化工具链【HTML】

本文详细解析了快速排序可视化工具的设计与实现,通过图形化界面直观展示算法的核心步骤。文章从算法原理、复杂度分析入手,重点介绍了可视化系统的设计(色彩编码、响应式布局、动画引擎)和核心算法实现(分区算法、非递归版本、基准选择策略)。工具采用状态颜色映射和动态渲染策略,支持交互控制(速度调节、状态管理)并进行了多项性能优化。该工具不仅具有教学价值,能清晰展示分区过程和递归调用,还支持四种基准策略对比和实时复杂度分析,是算法学习的优秀范例。项目采用响应式设计,适合各类设备使用。

迷茫的小徐

快速排序算法可视化展示项目实战

Qt是一个跨平台的C++库,广泛用于开发具有图形用户界面的软件应用程序。它包含了大量的类库,可以用于处理数据、进行网络通信、实现多线程以及创建图形用户界面。Qt以其优良的设计、高效的性能和跨平台的特性而著称。Qt采用的是信号与槽机制进行事件处理。信号是当某个特定事件发生时由对象发出的,而槽是响应信号的函数。在Qt中,信号和槽机制被用于对象间的通信。当一个信号被触发时,所有连接到该信号的槽函数将被执行。这种机制非常符合实际应用程序的事件驱动的开发模型。

南风寺山

快速排序算法可视化MFC界面实现

标准接口设计如下:/*** @brief 执行Lomuto分区操作* @param arr 输入数组(修改原数组)* @param low 当前处理区间的左边界(含)* @param high 当前处理区间的右边界(含)* @return pivot最终所在索引*/MFC(Microsoft Foundation Classes)是微软为简化 Windows 原生应用程序开发而提供的一套 C++ 类库,封装了 Win32 API 的复杂性。

高杉峻

史上最全VISIO图标集

那个10分的太贵了,我花钱买来,1分给大家.rar

LabAdmin
评论
收藏 已收藏
分享
返回顶部
代码kobe
万人千题 64,204 成员 15,010 内容