排序算法 -快速排序

news2024/11/18 17:48:01

文章目录

  • 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;  // 前指针,初始化为 left
    int cur = left + 1;  // 后指针,初始化为 left + 1
    int key = a[left];  // 选择基准元素为 left

    while (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.coloradmin.cn/o/2242944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

01、Spring MVC入门程序

概述&#xff1a; MVC(M&#xff1a;模型、V&#xff1a;视图、 C&#xff1a;控制器) 三层架构&#xff1a; 表现层&#xff08;Web层&#xff09;业务层&#xff08;Service层&#xff09;负责业务逻辑处理持久层&#xff08;Dao层&#xff09;负责和数据库交互 Spring MVC 作…

7.揭秘C语言输入输出内幕:printf与scanf的深度剖析

揭秘C语言输入输出内幕&#xff1a;printf与scanf的深度剖析 C语言往期系列文章目录 往期回顾&#xff1a; VS 2022 社区版C语言的安装教程&#xff0c;不要再卡在下载0B/s啦C语言入门&#xff1a;解锁基础概念&#xff0c;动手实现首个C程序C语言概念之旅&#xff1a;解锁关…

Android Osmdroid + 天地图 (一)

Osmdroid 天地图 前言正文一、配置build.gradle二、配置AndroidManifest.xml三、获取天地图的API Key① 获取开发版SHA1② 获取发布版SHA1 四、请求权限五、显示地图六、源码 前言 Osmdroid是一款完全开源的地图基本操作SDK&#xff0c;我们可以通过这个SDK去加一些地图API&am…

️️一篇快速上手 AJAX 异步前后端交互

AJAX 1. AJAX1.1 AJAX 简介1.2 AJAX 优缺点1.3 AJAX 前后端准备1.4 AJAX 请求基本操作1.5 AJAX 发送 POST 请求1.6 设置请求头1.7 响应 JSON 数据1.8 AJAX 请求超时与网络异常处理1.9 取消请求1.10 Fetch 发送 Ajax 请求 2. jQuery-Ajax2.1 jQuery 发送 Ajax 请求&#xff08;G…

2024年11月16日 星期六 重新整理Go技术

今日格言 坚持每天进步一点点~ 一个人也可以是一个团队~ 学习全栈开发, 做自己喜欢的产品~~ 简介 大家好, 我是张大鹏, 今天是2024年11月16日星期六, 很高兴在这里给大家分享技术. 今天又是休息的一天, 做了很多的思考, 整理了自己掌握的技术, 比如Java, Python, Golang,…

炼码LintCode--数据库题库(级别:简单;数量:55道)--刷题笔记_02

目录 炼码LintCode--数据库题库&#xff08;级别&#xff1a;简单&#xff1b;数量&#xff1a;55道&#xff09;--刷题笔记_023618 耗时前三的任务&#xff08;日期差&#xff09;题&#xff1a;sql&#xff1a;解释&#xff1a;DATEDIFF 天数差order by 别名TIMESTAMPDIFF 月…

洛谷刷题日记||基础篇8

#include <iostream> #include <vector> using namespace std;int N, M; // N为行数&#xff0c;M为列数 vector<vector<char>> field; // 表示田地的网格&#xff0c;每个元素是W或. vector<vector<bool>> visited; // 用来记录网格是否访…

在Ubuntu22.04上源码构建ROS noetic环境

Ubuntu22.04上源码构建ROS noetic 起因准备环境创建工作目录并下载源码安装编译依赖包安装ros_comm和rosconsole包的两个补丁并修改pluginlib包的CMakeLists的编译器版本编译安装ROS noetic和ros_test验证 起因 最近在研究VINS-Mono从ROS移植到ROS2&#xff0c;发现在编写feat…

从dos上传shell脚本文件到Linux、麒麟执行报错“/bin/bash^M:解释器错误:没有那个文件或目录”

[rootkylin tmp]#./online_update_wars-1.3.0.sh ba51:./online_update_wars-1.3.0.sh:/bin/bash^M:解释器错误:没有那个文件或目录 使用scp命令上传文件到麒麟系统&#xff0c;执行shell脚本时报错 “/bin/bash^M:解释器错误:没有那个文件或目录” 解决方法&#xff1a; 执行…

react+hook+vite项目使用eletron打包成桌面应用+可以热更新

使用Hooks-Admin的架构 Hooks-Admin: &#x1f680;&#x1f680;&#x1f680; Hooks Admin&#xff0c;基于 React18、React-Router V6、React-Hooks、Redux、TypeScript、Vite2、Ant-Design 开源的一套后台管理框架。https://gitee.com/HalseySpicy/Hooks-Adminexe桌面应用…

华东师范大学数学分析第五版PDF习题答案上册及下册

“数学分析”是数学专业最重要的一门基础课程&#xff0c;也是报考数学类专业硕士研究生的专业考试科目。为了帮助、指导广大读者学好这门课程&#xff0c;编者编写了与华东师范大学数学科学学院主编的《数学分析》(第五版)配套的辅导用书&#xff0c;以帮助读者加深对基本概念…

FineBI漏斗图分析转化率计算,需要获取当前节点和上一节点的转化率,需要获取错行值实现方案

FineBI漏斗图分析转化率计算&#xff0c;当前节点和上一节点的转化率&#xff0c;需要获取错行值 下面这张图大家很熟悉吧&#xff0c;非常经典的漏斗转化率分析。 从漏斗图看到需要计算转化率&#xff0c;都需要获取上一步漏斗的值&#xff0c;比如计算上一个省份的门店数量…

Solana 区块链的技术解析及未来展望 #dapp开发#公链搭建

随着区块链技术的不断发展和应用场景的扩展&#xff0c;性能和可拓展性成为各大公链竞争的关键因素。Solana&#xff08;SOL&#xff09;因其高吞吐量、低延迟和低成本的技术特性&#xff0c;在众多区块链项目中脱颖而出&#xff0c;被誉为“以太坊杀手”之一。本文将从技术层面…

FPGA开发-逻辑分析仪的应用-数字频率计的设计

目录 逻辑分析仪的应用 数字频率计的设计 -基于原理图方法 主控电路设计 分频器设计 顶层电路设计 数字系统开发不但需要进行仿真分析&#xff0c;更重要的是需要进行实际测试。 逻辑分析仪的应用 测试方式&#xff1a;&#xff08;1&#xff09;传统的测试方式&#…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统&#xff0c;旨在通过技能匹配的方式对招聘信息进行分析与预测&#xff0c;帮助求职者根据自身技能找到最合适的职位&#xff0c;同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

kubesphere环境-本地Harbor仓库+k8s集群(单master 多master)+Prometheus监控平台部署

前言&#xff1a;半月前在公司生产环境上离线部署了k8s集群Victoria Metrics(二开版)自研版夜莺 监控平台的搭建&#xff0c;下面我租用3台华为云服务器演示部署kubesphere环境-本地Harbor仓库k8s集群&#xff08;单master节点 & 单master节点&#xff09;Prometheus监控部…

车载诊断框架 --- UDS小白入门篇

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

强大的正则表达式——Easy

进入题目界面输入难度1后&#xff0c;让我们输入正则表达式&#xff08;regex&#xff09;&#xff1a; 目前不清楚题目要求&#xff0c;先去下载附件查看情况&#xff1a; import re import random# pip install libscrc import libscrcallowed_chars "0123456789()|*&q…

字节青训-小C的外卖超时判断、小C的排列询问

目录 一、小C的外卖超时判断 问题描述 测试样例 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 最终代码&#xff1a; 运行结果&#xff1a; 二、小C的排列询问 问题描述 测试样例 最终代码&#xff1a; 运行结果&#xff1a; ​编辑 一、小C的外卖超时判断…

游戏引擎学习第13天

视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…