排序算法 -快速排序

文章目录

  • 1. 快速排序(Quick Sort)
    • 1.1、 简介
    • 1.2、 快速排序的步骤
  • 2. Hoare 版本
    • 2.1、 基本思路
        • 1. 分区(Partition)
        • 2. 基准选择(Pivot Selection)
        • 3. 递归排序(Recursive Sorting)
    • 2.2 、代码实现
    • 2.3、 代码解释
      • 1. `GetMid` 函数:三数取中
        • 作用:
        • 逻辑:
      • 2. `HoreQuickSort` 函数:Hoare 分区法的快速排序
        • 主要步骤:
        • 代码整体总结
    • 2.4 、动画展示
  • 3. 挖坑版
    • 3.1、基本思路
    • 3.2、挖坑法步骤
    • 3.3、代码实现
    • 3.4、动画展示
  • 4. 前后指针
    • 4.1、基本思路
    • 4. 2、步骤
    • 4.3 、代码中的实现:
      • 解释:
    • 4. 4 、动画展示

1. 快速排序(Quick Sort)

1.1、 简介

快速排序(Quick Sort)是一种高效的排序算法,通常用于大数据集的排序。它由英国计算机科学家 Tony Hoare 在 1960 年提出,并基于分治法(Divide and Conquer)的思想来排序数组或列表。

1.2、 快速排序的步骤

  1. 选择基准(Pivot)

    • 从数组中选择一个元素作为基准。基准的选择方法会影响算法的性能,常用的基准选择策略包括:
      • 选择第一个元素
      • 选择最后一个元素
      • 选择中间元素
      • 随机选择
      • 三数取中法(选择第一个、最后一个和中间位置的元素中位数作为基准)
  2. 划分(Partition)

    • 将数组分为两部分,使得:
      • 一部分的元素都小于等于基准;
      • 另一部分的元素都大于基准。
    • 此时,基准元素已经在排序后的正确位置上。
  3. 递归排序

    • 对基准左侧的子数组和右侧的子数组递归地进行快速排序。
    • 当子数组长度为 1 或 0 时,递归结束,此时该部分已经有序。

2. Hoare 版本

2.1、 基本思路

这个代码的基本思路是实现快速排序(QuickSort)算法的一个优化版本,主要包含 分区递归基准选择 三个关键步骤。以下是简化后的基本思路:

1. 分区(Partition)

快速排序的核心是“分区”操作。它通过选择一个基准值,将数组分成左右两部分,使得左侧的元素都小于等于基准,右侧的元素都大于等于基准。完成分区后,基准元素会处于它排序后的最终位置。

2. 基准选择(Pivot Selection)

在分区过程中,基准的选择直接影响算法的效率。理想情况下,基准会将数组分成大小相近的两部分,避免极端情况的出现。这里使用 三数取中法 来选择基准,即取数组左端、中间、右端的三个值中间的那个作为基准值。这样能够减少最坏情况的出现(例如数组本身有序时的情况),提高算法的平均性能。

3. 递归排序(Recursive Sorting)

完成分区后,基准两侧的子数组还未有序,需要对每个子数组继续进行快速排序。通过递归调用,将每个子数组逐步分成更小的部分,最终达到完全有序。递归的终止条件是数组长度为 1 或 0,这种情况直接返回,无需再排序。

2.2 、代码实现

#include <stdio.h>// 交换函数
void Swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}// 三数取中函数,返回左、中、右三者的中间值索引
int GetMid(int *a, int left, int right) {int mid = left + (right - left) / 2;if (a[left] > a[right]) {Swap(&a[left], &a[right]);}if (a[mid] > a[right]) {Swap(&a[mid], &a[right]);}if (a[left] < a[mid]) {Swap(&a[left], &a[mid]);}return left;  // 返回基准元素的索引
}// Hoare 分区法的快速排序
void HoreQuickSort(int *a, int left, int right) {if (left >= right) {return;}int begin = left, end = right;int key = GetMid(a, left, right);  // 三数取中并将基准值放到left位置Swap(&a[left], &a[key]);           // 将基准交换到左端作为参考while (begin < end) {// 从右往左找到第一个小于基准的元素while (begin < end && a[end] >= a[left]) {--end;}// 从左往右找到第一个大于基准的元素while (begin < end && a[begin] <= a[left]) {++begin;}// 交换左右不符合顺序的元素if (begin < end) {Swap(&a[begin], &a[end]);}}// 最后将基准元素放到分区点Swap(&a[left], &a[begin]);// 递归排序左右两部分HoreQuickSort(a, left, begin - 1);HoreQuickSort(a, begin + 1, right);
}// 打印数组
void print_array(int *a, int size) {for (int i = 0; i < size; i++) {printf("%d ", a[i]);}printf("\n");
}// 主函数
int main() {int arr[] = {10, 7, 8, 9, 1, 5};int n = sizeof(arr) / sizeof(arr[0]);printf("原始数组: ");print_array(arr, n);HoreQuickSort(arr, 0, n - 1);printf("排序后数组: ");print_array(arr, n);return 0;
}

2.3、 代码解释

1. GetMid 函数:三数取中

int GetMid(int *a, int left, int right) {int mid = left + (right - left) / 2; // 计算中间索引if (a[left] > a[right]) {Swap(&a[left], &a[right]); // 保证a[left] <= a[right]}if (a[mid] > a[right]) {Swap(&a[mid], &a[right]);   // 保证a[mid] <= a[right]}if (a[left] < a[mid]) {Swap(&a[left], &a[mid]);    // 保证a[left] <= a[mid]}return left; // 返回左索引位置作为基准索引
}
作用:

GetMid 函数的目的是在 左端、中间、右端 三个元素中选出中间值作为基准,避免在近乎有序的数组中使用一个极值作为基准,从而减少分区的不平衡问题。

逻辑:
  • a[left] > a[right] 时交换,确保 a[left]a[right] 小。
  • a[mid] > a[right] 时交换,确保 a[mid] 也比 a[right] 小。
  • a[left] < a[mid] 时交换,这样 a[left] 成为了中间值。
  • 最后返回 left,将三数取中的结果放到 a[left],作为基准值。

2. HoreQuickSort 函数:Hoare 分区法的快速排序

void HoreQuickSort(int *a, int left, int right) {if (left >= right) {return;  // 基本情况:如果只有一个元素或无元素,直接返回}int begin = left, end = right; // 初始化分区指针int key = GetMid(a, left, right);  // 选择基准并取三数中值Swap(&a[left], &a[key]);           // 将基准交换到左端while (begin < end) {// 从右向左找到第一个小于基准的元素while (begin < end && a[end] >= a[left]) {--end;}// 从左向右找到第一个大于基准的元素while (begin < end && a[begin] <= a[left]) {++begin;}// 交换找到的元素if (begin < end) {Swap(&a[begin], &a[end]);}}// 最后将基准元素放到最终位置Swap(&a[left], &a[begin]);// 递归排序左右两部分HoreQuickSort(a, left, begin - 1);HoreQuickSort(a, begin + 1, right);
}
主要步骤:
  1. 基准选择与初始化

    • 使用 GetMid 进行三数取中法选择基准,减少最坏情况下的不平衡。
    • 将基准交换到 left 位置,方便分区操作。
  2. 分区过程

    • beginend 分别从左、右两端向中间移动。
    • 从右向左找到第一个小于基准的元素,将 end 移动到该位置。
    • 从左向右找到第一个大于基准的元素,将 begin 移动到该位置。
    • 如果 begin < end,交换 a[begin]a[end],确保小于基准的值在左侧,大于基准的值在右侧。
    • 循环直到 begin >= end,此时 begin 指向的元素是分区的正确位置。
  3. 基准位置调整

    • a[left](即基准值)和 a[begin] 交换,使基准值放置在分区中间的正确位置。
  4. 递归调用

    • 对基准左侧(leftbegin - 1)和右侧(begin + 1right)的子数组分别递归调用 HoreQuickSort 进行排序,直到整个数组有序。
代码整体总结
  • Hoare 分区法:通过双指针从两端向中间扫描,交换不符合条件的元素,提高分区效率。
  • 三数取中法:选基准时用三数取中法,减少极端情况的出现。
  • 递归快速排序:分区后对左右两部分递归排序,直到每个子数组有序。

2.4 、动画展示

hore

3. 挖坑版

快速排序挖坑法是一种直观且高效的排序算法实现方式,它基于分治策略,通过递归地将数组分成较小的子数组来排序。以下是关于快速排序挖坑法的详细解释:

3.1、基本思路

快速排序的基本思想是选择一个基准值(pivot),然后将数组分成两部分:一部分包含所有小于基准值的元素,另一部分包含所有大于基准值的元素。接着,递归地对这两部分进行同样的操作,直到整个数组有序。

3.2、挖坑法步骤

  1. 选择基准值

    • 通常选择数组的第一个元素、最后一个元素或中间元素作为基准值。为了简单起见,这里以第一个元素为例。
    • 记住基准值的位置,这个位置相当于一个“坑”。
  2. 初始化指针

    • 设置两个指针,left和right,分别指向数组的最左和最右两个元素。
  3. 从右向左遍历

    • 从right指针开始,将指针所指向的元素与基准值进行比较。
    • 如果元素大于基准值,则right指针向左移动。
    • 如果元素小于基准值,则将该元素填入坑中(即基准值原本的位置),并将该元素原本的位置视为新的坑。同时,left指针向右移动一位。
  4. 从左向右遍历

    • 从left指针开始,将指针所指向的元素与基准值进行比较(注意,此时基准值已被某个元素替换,因此实际上是与该元素进行比较)。
    • 如果元素小于基准值(实际上是小于刚才被填入坑中的那个元素,因为基准值已被替换),则left指针向右移动。
    • 如果元素大于基准值,则将该元素填入坑中,并将该元素原本的位置视为新的坑。同时,right指针向左移动一位。
  5. 重复步骤3和4

    • 继续按照上述步骤进行遍历,直到left和right指针重合。
  6. 放入基准值

    • 当left和right指针重合时,将基准值(或最初被选出的那个基准值的值,如果它在遍历过程中被替换了的话)放入重合的位置。
    • 此时,基准值左边的所有元素都小于它,右边的所有元素都大于它。
  7. 递归排序

    • 对基准值左边和右边的子数组递归地进行上述操作,直到整个数组有序。

3.3、代码实现

// 挖坑法快速排序
void HoleQuickSort(int* arr, int left, int right)
{if (left >= right)return;// 使用第一个元素作为基准值(也可以选择其他方式,如三数取中)int pivotValue = arr[left];int holeIndex = left; // 挖坑的位置,初始化为基准值的位置int scanLeft = left, scanRight = right; // 左右扫描指针// 从右向左扫描,找到第一个小于基准值的元素while (scanRight > scanLeft){// 从右向左找到第一个小于基准值的元素while (scanRight > scanLeft && arr[scanRight] >= pivotValue)scanRight--;// 如果找到,将该元素放到坑里(holeIndex位置)if (scanRight > scanLeft){arr[holeIndex] = arr[scanRight];holeIndex = scanRight; // 更新坑的位置到当前元素的位置}// 从左向右扫描,找到第一个大于基准值的元素while (scanRight > scanLeft && arr[scanLeft] <= pivotValue)scanLeft++;// 如果找到,将该元素放到坑里(此时坑的位置可能已经被更新)if (scanRight > scanLeft){arr[holeIndex] = arr[scanLeft];holeIndex = scanLeft; // 再次更新坑的位置}}// 当左右扫描指针相遇时,将基准值放到坑里arr[holeIndex] = pivotValue;// 递归排序基准值左右两侧的子数组HoleQuickSort(arr, left, holeIndex - 1);HoleQuickSort(arr, holeIndex + 1, right);
}// 辅助函数:交换两个整数的值(此函数在您的原始代码中已提供,但为完整性而保留)
void swap(int* a, int* b)
{int temp = *a;*a = *b;*b = temp;
}

3.4、动画展示

w

4. 前后指针

4.1、基本思路

快速排序的前后指针法(也叫“双指针法”)是快速排序中的一种常见分区方法。这个方法通过两个指针,分别从数组的左右两端开始,来逐步划分数组,使得数组中的元素相对于一个基准元素(通常是数组的第一个元素)进行排序。

4. 2、步骤

  1. 选择基准元素:选择数组中的一个元素作为基准(pivot)。在你给出的代码中,基准是数组的第一个元素 key

  2. 设置两个指针

    • 前指针(pre):从左边开始,指向当前已经被正确分区的区域的末尾。初始时,pre 指向基准元素。
    • 后指针(cur):从左边的第二个元素开始,遍历整个数组,用来扫描比基准小的元素。
  3. 扫描并交换

    • 前指针会不断向右移动,找到一个比基准小的元素。
    • 后指针会遍历数组,直到找到一个比基准大的元素。
    • 如果后指针找到一个大于基准的元素,同时前指针找到了一个小于基准的元素,则交换这两个元素。这保证了所有小于基准的元素都在基准的左边,所有大于基准的元素都在基准的右边。
  4. 完成分区

    • 当两个指针相遇时,或者后指针扫描到数组的右边界时,前后指针停止。
    • 这时,将基准元素和 pre 指针所指向的元素交换。这样,基准元素就放置在了正确的位置上,左边是比基准小的元素,右边是比基准大的元素。
  5. 递归排序:对基准元素左边和右边的子数组继续使用相同的分区方法递归进行排序,直到子数组的大小为 1 或 0。

4.3 、代码中的实现:

void FbQuickSort(int* a, int left, int right)
{if (left >= right)return;int pre = left;  // 前指针,初始化为 leftint cur = left + 1;  // 后指针,初始化为 left + 1int key = a[left];  // 选择基准元素为 leftwhile (cur <= right){if (a[cur] < key)  // 如果当前元素小于基准元素{pre++;  // 前指针右移Swap(&a[cur], &a[pre]);  // 交换当前元素与前指针指向的元素}cur++;  // 后指针继续右移}// 最后将基准元素与前指针指向的元素交换Swap(&a[left], &a[pre]);// 递归对左边和右边的子数组进行快速排序FbQuickSort(a, left, pre - 1);FbQuickSort(a, pre + 1, right);
}

解释:

  1. 基准元素key = a[left],我们选择数组的第一个元素作为基准。
  2. 前后指针初始化pre = leftcur = left + 1pre 记录当前已经排序好的区域的边界,cur 用来遍历剩余的部分。
  3. 主循环while (cur <= right) 让后指针 cur 遍历整个数组。在每次扫描中,检查当前元素是否小于基准元素,如果是,则将其与前指针 pre 所指向的元素交换,同时 pre 自增。
  4. 基准交换Swap(&a[left], &a[pre]) 将基准元素交换到其最终位置,这时 pre 的位置已经确定了基准元素应该放置的位置。左边是比基准小的元素,右边是比基准大的元素。
  5. 递归调用:对基准元素左右两侧的子数组递归进行快速排序。

4. 4 、动画展示

pb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/471466.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UAC2.0 speaker——同时支持 16bit,24bit 和 32bit

文章目录 同时支持 16bit,24bit 和 32bit配置描述符集合描述符结构位数切换16bit 选择24bit 选择32bit 选择枚举效果同时支持 16bit,24bit 和 32bit 在一个 USB speaker 设备中同时支持 16bit, 24bit 和 32bit。 配置描述符集合 09 02 E9 00 02 01 00 80 32 08 0B 00 02

conda创建 、查看、 激活、删除 python 虚拟环境

1、创建 python 虚拟环境 ,假设该环境命名为 “name”。 conda create -n name python3.11 2、查看 python 虚拟环境。 conda info -e 3、激活使用 python 虚拟环境。 conda activate name 4、删除 python 虚拟环境 conda remove -n name --all ​​ 助力快速掌握数据集…

三周精通FastAPI:37 包含 WSGI - Flask,Django,Pyramid 以及其它

官方文档&#xff1a;https://fastapi.tiangolo.com/zh/advanced/wsgi/ 包含 WSGI - Flask&#xff0c;Django&#xff0c;其它 您可以挂载多个 WSGI 应用&#xff0c;正如您在 Sub Applications - Mounts, Behind a Proxy 中所看到的那样。 为此, 您可以使用 WSGIMiddlewar…

微服务即时通讯系统的实现(客户端)----(1)

目录 1. 项目整体介绍1.1 项目概况1.2 界面预览和功能介绍1.3 技术重点和服务器架构 2. 项目环境搭建2.1 安装Qt62.3 安装vcpkg2.3 安装protobuf2.4 构建项目2.5 配置CMake属性 3. 项目核心数据结构的实现3.1 创建data.h存放核心的类3.2 工具函数的实现3.3 创建编译开关 4. 界面…

MyBatis——增删查改(XML 方式)

1. 查询 1.1. 简单查询 使用注解的方式主要是完成一些简单的增删查改功能&#xff0c;如果要实现复杂的 SQL 功能&#xff0c;还是建议使用 XML 来配置映射语句&#xff0c;将 SQL 语句写在 XML 配置文件中 如果要操作数据库&#xff0c;需要做以下的配置&#xff0c;与注解…

A029-基于Spring Boot的物流管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

华为路由策略配置

一、AS_Path过滤 要求&#xff1a; AR1与AR2、AR2与AR3之间建立EBGP连接 AS10的设备和AS30的设备无法相互通信 1.启动设备 2.配置IP地址 3.配置路由器的EBGP对等体连接&#xff0c;引入直连路由 [AR1]bgp 10 [AR1-bgp]router-id 1.1.1.1 [AR1-bgp]peer 200.1.2.2 as-nu…

如何向函数模块 FM 中传递 Range 参数

有时候需要在选择屏幕之后调用一个函数模块&#xff0c;那么如果利用 SE37 在函数模块定义 Range 参数呢。 解决方法很简单&#xff0c;系统有很多预定义的 Range_* 类型&#xff1a; 如上图&#xff0c;这里有很常用的 Range 结构&#xff0c;如订单号、发票号、公司代码等等…

工作和学习遇到的技术问题

写在前面 记录工作和学习遇到的技术问题,以求再次遇到可以快速解决。 1&#xff1a;Ubuntu TSL换源报错&#xff1a;Err:1 http://mirrors.aliyun.com/ubuntu focal InRelease 执行如下操作&#xff08;已经操作的则忽略&#xff09;&#xff0c;首先在文件/etc/apt/sources…

研究生如何远控实验室电脑?远程办公功能使用教程

如果你是研究生&#xff0c;是不是会遇到需要远程控制实验室电脑进行查看文献、调代码和拉数据的时候&#xff1f;有时候就是这么棘手&#xff0c;不过你可以借助一些工具来帮助你随时随地远控实验室电脑。这样就不用担心导师催促&#xff0c;无法及时完成科研了。常见的工具比…

重卡穿越商都,ROG DAY 2024郑州站高燃来袭

野塘菡萏正新秋,红藕香中过郑州!2024年11月9日~10日,ROG DAY 2024信仰集结的号角正式吹响,首战据点落地郑州局外太格茂。炫酷涂装的战车如同未来战士般震撼登陆,ROG硬核科技闪耀亮相,现场氛围瞬间点燃!活动现场人流不息,年轻学子、数码爱好者、极客玩家、科技博主以及周末悠闲惬…

web安全测试渗透案例知识点总结(上)——小白入狱

目录 一、Web安全渗透测试概念详解1. Web安全与渗透测试2. Web安全的主要攻击面与漏洞类型3. 渗透测试的基本流程 二、知识点详细总结1. 常见Web漏洞分析2. 渗透测试常用工具及其功能 三、具体案例教程案例1&#xff1a;SQL注入漏洞利用教程案例2&#xff1a;跨站脚本&#xff…

浪潮信息“源”Embedding模型登顶MTEB榜单第一名

在自然语言处理&#xff08;NLP&#xff09;和机器学习领域&#xff0c;Embedding模型是将文本数据转换为高维向量表示的核心技术&#xff0c;直接影响NLP任务&#xff08;如文本分类、情感分析等&#xff09;的效果&#xff0c;对于提升模型性能和深入理解文本语义具有至关重要…

catchadmin-webman 宝塔 部署

1&#xff1a;宝塔的php 中删除禁用函数 putenv 问题&#xff1a; 按照文档部署的时候linux&#xff08;php&#xff09; vue (本地) 无法访问后端api/login 的接口 。 解决办法&#xff1a; webman 没有配置nginx 反向代理 配置就能正常访问了

【AutoGen 】简介

学习笔记AutoGen。它可以使用多个代理来开发 LLM 应用程序,这些代理可定制、可相互对话,可在各种模式下运行,且无缝允许人的参与,进一步在更大程度上为开发者提供助力。AutoGen 智能应用开发(一)|AutoGen 基础 学习笔记

【月之暗面kimi-注册/登录安全分析报告】

前言 由于网站注册入口容易被机器执行自动化程序攻击&#xff0c;存在如下风险&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露&#xff0c;不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 &#xff0c;造成用户无法登陆、注册&#xff0c;大量收到垃圾短信的…

统信UOS开发接口DTK

DTK(Development ToolKit)是基于 Qt 开发的简单且实用的通用开发框架。提供丰富的开发接口与支持工具,能有效提升开发效率。 文章目录 一、简介DTK 常见模块介绍概述二、框架创建开发环境准备使用 cmake三、常见模块窗口和对话框一、简介 DTK 常见模块介绍 概述 DTK(Dev…

城市轨道交通数据可视化的应用与优势

通过图扑可视化技术将复杂的数据转化为易于理解的图像&#xff0c;助力交通管理者优化线路规划、提升运营效率和乘客信息服务。轨道交通管理者能够更直观地分析乘客流量、运营效率等关键指标&#xff0c;从而优化线路设计与调度&#xff0c;提高服务质量&#xff0c;为乘客提供…

【JavaEE初阶 — 多线程】生产消费模型 阻塞队列

1. 阻塞队列 (1) 阻塞队列 1. 概念 阻塞队列是一种特殊的队列&#xff0c;也遵守"先进先出"的原则&#xff1b;阻塞队列能是一种线程安全的数据结构&#xff0c;主要用来阻塞队列的插入和获取操作&#xff1a; 当队列满了的时候&#xff0c;插入操作会被…

重构开发之道,Blackbox.AI为技术注入智能新动力

本文目录 一、引言二、Blackbox.AI实战体验2.1 基于网页界面生成前端代码进行应用开发2.2 与AI助手实现实时智能对话2.3 重塑大型文件交互方式2.4 链接Github仓库进行对话编程 三、总结 一、引言 在生产力工具加速进化的浪潮中&#xff0c;Blackbox.AI开始崭露头角&#xff0c…