手撕快排——三种实现方法(附动图及源码)

news2024/12/28 5:01:15

🤖💻👨‍💻👩‍💻🌟🚀

🤖🌟 欢迎降临张有志的未来科技实验室🤖🌟

专栏:数据结构      

👨‍💻👩‍💻 先赞后看,已成习惯👨‍💻👩‍💻

👨‍💻👩‍💻 创作不易,多多支持👨‍💻👩‍💻

🚀 启动创新引擎,揭秘数据结构的密码🚀


快速排序(Quicksort)是一种高效的排序算法,广泛应用于各种场景。它采用分治法(Divide and Conquer)策略,将一个数组分成两个子数组,然后递归地对这两个子数组进行排序。本文将介绍三种快速排序的实现方法,并附上相应的源码。

目录

⚙️一、快速排序的基本原理

🧠二、算法性能

📚三、实现

1.霍尔方法(hoare)

实现步骤:

1. 随机化基准值选择

2. 三数取中法

3.插入排序小数组

4. 尾递归优化

2. 挖坑法 (Digging Hole Method) - 优化版

实现步骤:

3. 前后指针法 (Two-Pointer Method) - 优化版

实现步骤:

💡优化要点回顾


⚙️一、快速排序的基本原理


快速排序的基本步骤如下:

  1. 从数组中选择一个基准元素(pivot)
  2. 将数组分成两个子数组:小于基准元素的部分和大于基准元素的部分。
  3. 递归地对这两个子数组进行排序。
  4. 合并两个已排序的子数组和基准元素,形成最终的排序结果。

🧠二、算法性能


  1. 时间复杂度:

    • 最好情况: 当输入数组已经接近有序或者每次分区都能均匀地将数组分成大小相近的两部分时,快速排序的时间复杂度为 O(n log n)
    • 平均情况: 在大多数情况下,快速排序的时间复杂度为 O(n log n)
    • 最坏情况: 当输入数组完全逆序或者每次都选择最小或最大值作为基准时,时间复杂度退化为 O(n^2)后文我们将给出优化方案
  2. 空间复杂度:

    • 快速排序是一种原地排序算法,这意味着它不需要额外的存储空间来保存数据。但是,由于使用了递归,递归栈会占用一定的空间,因此它的空间复杂度为 O(log n)(递归调用的深度)。

📚三、实现


1.霍尔方法(hoare)

霍尔方法是快速排序原始版本中使用的分区方法。这种方法使用两个指针,一个从左向右移动,另一个从右向左移动,直到它们相遇。霍尔方法的主要优点是不需要额外的存储空间,并且通常比其他方法更高效。

实现步骤:
  1. 选择基准值:通常选择数组中的最后一个元素。
  2. 初始化指针:设置 i 为左端点减一,j 为右端点加一。
  3. 移动指针
    • 从左向右移动 i,直到找到一个大于或等于基准值的元素。
    • 从右向左移动 j,直到找到一个小于或等于基准值的元素。
  4. 交换元素:如果 i 小于等于 j,则交换 i 和 j 指向的元素,并继续移动指针。
  5. 重复步骤 3 和 4,直到 i 大于 j
int hoare_partition(int arr[], int low, int high) {
    int pivot = arr[low];
    int i = low - 1;
    int j = high + 1;

    while (true) {
        do {
            i++;
        } while (arr[i] < pivot);

        do {
            j--;
        } while (arr[j] > pivot);

        if (i >= j)
            return j;

        swap(&arr[i], &arr[j]);
    }
}

这并不完美,接下来我将给出优化方案

1. 随机化基准值选择

问题1:如果基准值总是选择数组的第一个或最后一个元素,那么在最坏情况下(例如数组已经排序好或几乎排序好)时间复杂度会退化为 O(n^2)。

解决方案:通过随机选择基准值来减少最坏情况出现的概率。这可以通过在分区前随机选择数组中的一个元素作为基准值来实现。

int random_partition(int arr[], int low, int high) {
    srand(time(0));  // 种子时间
    int rand_pivot = low + rand() % (high - low + 1);
    swap(&arr[rand_pivot], &arr[high]);  // 交换随机基准值到数组末尾
    return partition(arr, low, high);
}
2. 三数取中法

问题:随机化虽然可以减少最坏情况的概率,但在某些情况下仍然可能选择到极端的基准值。

解决方案:三数取中法选择数组的第一个元素、中间元素和最后一个元素的中位数作为基准值,这样可以进一步减少最坏情况出现的概率。

int median_of_three(int arr[], int low, int high) {
    int mid = (low + high) / 2;
    if (arr[low] > arr[mid]) swap(&arr[low], &arr[mid]);
    if (arr[low] > arr[high]) swap(&arr[low], &arr[high]);
    if (arr[mid] > arr[high]) swap(&arr[mid], &arr[high]);

    // 中间元素作为基准值
    swap(&arr[mid], &arr[high]);
    return partition(arr, low, high);
}
3.插入排序小数组

问题:对于小规模数组,快速排序的递归开销较大。

解决方案:当数组的大小低于某个阈值(例如 10 或 15)时,改用插入排序。插入排序在小规模数组中效率更高。

void quickSortWithInsertion(int arr[], int low, int high) {
    if (low < high) {
        if (high - low < 10) {  // 使用插入排序
            insertionSort(arr, low, high);
        } else {
            int pi = hoare_partition(arr, low, high);
            quickSortWithInsertion(arr, low, pi - 1);
            quickSortWithInsertion(arr, pi + 1, high);
        }
    }
}

void insertionSort(int arr[], int low, int high) {
    for (int i = low + 1; i <= high; i++) {
        int key = arr[i];
        int j = i - 1;

        while (j >= low && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}
4. 尾递归优化

问题:快速排序使用递归来实现,递归深度过大可能导致栈溢出。

解决方案:通过递归只处理一边的子数组,而另一边使用迭代处理,可以减少递归调用栈的深度。

void quickSortOptimized(int arr[], int low, int high) {
    while (low < high) {
        int pi = partition(arr, low, high);

        // 只递归处理较短的那一边
        if (pi - low < high - pi) {
            quickSortOptimized(arr, low, pi - 1);
            low = pi + 1;
        } else {
            quickSortOptimized(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}

2. 挖坑法 (Digging Hole Method) - 优化版

挖坑法通过预留一个“坑”来放置基准值,以减少不必要的交换次数。下面是一个结合了随机化基准值选择、尾递归优化以及对于小数组使用插入排序的优化版本。

实现步骤:
  1. 选择基准值:通常选择数组中的最后一个元素。
  2. 初始化“坑”:将基准值移至数组末尾,形成一个“坑”。
  3. 遍历数组:从左向右遍历数组。
    • 如果遇到小于基准值的元素,将其放入“坑”,然后将“坑”向前移动一位。
  4. 重复步骤 3,直到遍历结束。
  5. 基准值就位:将基准值放入“坑”中

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 交换函数
void swap(int *xp, int *yp) {
    int temp = *xp;
    *xp = *yp;
    *yp = temp;
}

// 随机化基准值选择
int randomized_partition(int arr[], int low, int high) {
    srand(time(0));
    int rand_pivot = low + rand() % (high - low + 1);
    swap(&arr[rand_pivot], &arr[high]);

    int pivot = arr[high];
    int hole = low - 1;
    for (int i = low; i < high; i++) {
        if (arr[i] <= pivot) {
            hole++;
            swap(&arr[hole], &arr[i]);
        }
    }
    swap(&arr[hole + 1], &arr[high]);
    return hole + 1;
}

// 快速排序函数
void quickSortOptimized(int arr[], int low, int high) {
    while (low < high) {
        int pi = randomized_partition(arr, low, high);

        if (pi - low < high - pi) {
            quickSortOptimized(arr, low, pi - 1);
            low = pi + 1;
        } else {
            quickSortOptimized(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}

// 插入排序小数组
void insertionSort(int arr[], int low, int high) {
    for (int i = low + 1; i <= high; i++) {
        int key = arr[i];
        int j = i - 1;

        while (j >= low && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

// 主函数
int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSortOptimized(arr, 0, n - 1);

    printf("Sorted array: \n");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

3. 前后指针法 (Two-Pointer Method) - 优化版

前后指针法是一种直观的分区方法,类似于霍尔方法,但使用两个指针来跟踪小于基准值和大于基准值的边界。

实现步骤:
  1. 选择基准值:通常选择数组中的最后一个元素。
  2. 初始化指针:设置 i 为左端点,j 也为左端点。
  3. 遍历数组
    • 从左向右移动 j,直到找到一个大于基准值的元素。
    • 如果 arr[j] 小于等于基准值,将 arr[j] 与 arr[i] 交换,并将 i 向右移动一位。
  4. 重复步骤 3,直到 j 达到右端点。
  5. 基准值就位:将基准值与 arr[i] 交换
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 交换函数
void swap(int *xp, int *yp) {
    int temp = *xp;
    *xp = *yp;
    *yp = temp;
}

// 随机化基准值选择
int randomized_partition(int arr[], int low, int high) {
    srand(time(0));
    int rand_pivot = low + rand() % (high - low + 1);
    swap(&arr[rand_pivot], &arr[high]);

    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

// 快速排序函数
void quickSortOptimized(int arr[], int low, int high) {
    while (low < high) {
        int pi = randomized_partition(arr, low, high);

        if (pi - low < high - pi) {
            quickSortOptimized(arr, low, pi - 1);
            low = pi + 1;
        } else {
            quickSortOptimized(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}

// 插入排序小数组
void insertionSort(int arr[], int low, int high) {
    for (int i = low + 1; i <= high; i++) {
        int key = arr[i];
        int j = i - 1;

        while (j >= low && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

// 主函数
int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSortOptimized(arr, 0, n - 1);

    printf("Sorted array: \n");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

💡优化要点回顾

  1. 随机化基准值选择:通过随机选择基准值来减少最坏情况出现的概率,从而提高快速排序的平均性能。
  2. 尾递归优化:通过迭代而非递归来减少递归调用栈的深度,从而降低栈溢出的风险。
  3. 插入排序小数组:对于小规模数组使用插入排序,因为插入排序在这种情况下通常更快。

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

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

相关文章

【C++】STL——list

前言 本篇博客我们接着来理解一个STL库里的list链表的结构&#xff0c;根据前面数据结构的铺垫&#xff0c;理解这个结构相对比较容易。我们来一起看看吧 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;C 若有问题 评论区见&#x1f4dd; &#x1f38…

中国与中南半岛国家多国语言系统开发i18n配置老挝、柬埔寨语言配置

前言 当下中国与中南半岛国家经济合作密切&#xff0c;同时也需要软件系统&#xff0c;多国使用系统需要实现多语言&#xff0c;我们团队最近也接到一个中、老、柬三国的业务软件&#xff0c;需要将软件做成三个国家语言。然后我们网上收i18n的老、柬的语言包命名&#xff0c;…

计算机毕业设计 美妆神域网站 美妆商城系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

j2:基于pytorch的resnet实验:鸟类分类

基于pytorch的resnet实验&#xff1a;鸟类分类 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 Ⅰ Ⅰ Ⅰ Introduction&#xff1a; 本文为机器学习使用resnet实现鸟类图片分类的实验&#xff0c;素材来自网…

跟李沐学AI:目标检测的常用算法

区域神经网络R-CNN 使用启发式搜索算法来选择锚框 -> 使用预训练模型来对每个锚框抽取特征 -> 训练一个SVM对类别进行分类 -> 训练一个线性回归模型来预测边缘框偏移 锚框大小不一&#xff0c;如何将不同的锚框统一为一个batch? -> 兴趣区域池化层 兴趣区域(RoI…

界面优化 - QSS

目录 1、背景介绍 2、基本语法 3、QSS 设置方式 3.1 指定控件样式设置 代码示例: 子元素受到影响 3.2 全局样式设置 代码示例: 使用全局样式 代码示例: 样式的层叠特性 代码示例: 样式的优先级 3.3 从文件加载样式表 代码示例: 从文件加载全局样式 3.4 使用 Qt Desi…

最新UI六零导航系统源码 | 多模版全开源

六零导航页 (LyLme Spage) 致力于简洁高效无广告的上网导航和搜索入口&#xff0c;支持后台添加链接、自定义搜索引擎&#xff0c;沉淀最具价值链接&#xff0c;全站无商业推广&#xff0c;简约而不简单。 使用PHPMySql&#xff0c;增加后台管理 多模板选择&#xff0c;支持在…

MySQL基础练习题46-每位经理的下属员工数量

目录 题目 准备数据 分析数据 总结 题目 我们将至少有一个其他员工需要向他汇报的员工&#xff0c;视为一个经理。 返回需要听取汇报的所有经理的 ID、名称、直接向该经理汇报的员工人数&#xff0c;以及这些员工的平均年龄&#xff0c;其中该平均年龄需要四舍五入到最接近…

【网络】IP分片与路径MTU发现

目录 MTU值 IP分片与重组 路径MTU发现 路径MTU发现原理 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 相关文章&#xff1a;【网络】从零认识IPv4-CSDN博客 MTU值 由于物理层的硬件限制&#xff0c;为了使网络性能最优&#xff0c;在数据链路层会有一个MTU值&#xff0…

算法【Java】—— 双指针算法

双指针算法 常见的双指针有对撞指针&#xff0c;快慢指针以及前后指针&#xff08;这个前后指针是指两个指针都是从从一个方向出发&#xff0c;去往另一个方法&#xff0c;也可以认为是小学学习过的两车并行&#xff0c;我也会叫做同向指针&#xff09;&#xff0c;在前后指针…

Python3网络爬虫开发实战(10)模拟登录(需补充账号池的构建)

文章目录 一、基于 Cookie 的模拟登录二、基于 JWT 模拟登入三、账号池四、基于 Cookie 模拟登录爬取实战五、基于JWT 的模拟登录爬取实战六、构建账号池 很多情况下&#xff0c;网站的一些数据需要登录才能查看&#xff0c;如果需要爬取这部分的数据&#xff0c;就需要实现模拟…

KNN图像识别实例--手写数字识别

目录 前言 一、导入库 二、导入图像并处理 1.导入图像 2.提取出图像中的数字 3.将列表转换成数组 4.获取特征数据集 5.获取标签数据 三、使用KNN模型 1.创建KNN模型并训练 2.KNN模型出厂前测试 3.使用测试集对KNN模型进行测试 四、传入单个图像&#xff0c;使用该模…

叉车高位盲区显示器 无线摄像头免打孔 视线遮挡的解决方案

叉车作业货叉叉货时&#xff0c;货叉升降无法看清位置&#xff0c;特别是仓储的堆高车&#xff0c;司机把头探出去才勉强可以靠经验找准方位&#xff01;一个不小心就可能叉歪了&#xff0c;使货物倾斜、跌落等等&#xff0c;从而发生事故&#xff01;如何将隐患扼杀&#xff0…

【JAVA入门】Day21 - 时间类

【JAVA入门】Day21 - 时间类 文章目录 【JAVA入门】Day21 - 时间类一、JDK7前的时间相关类1.1 Date1.2 SimpleDateFormat1.3 Calendar 二、JDK8新增的时间相关类2.1 Date 相关类2.1.1 ZoneId 时区2.1.2 Instant 时间戳2.1.3 ZoneDateTime 带时区的时间 2.2 DateTimeFormat 相关…

刷题DAY7

三个数的排序 题目&#xff1a;输入三个整数x&#xff0c;y&#xff0c;z&#xff0c;请把这三个数由小到大输出 输入&#xff1a;输入数据包含3个整数x&#xff0c;y&#xff0c;z&#xff0c;分别用逗号隔开 输出&#xff1a;输出由小到大排序后的结果&#xff0c;用空格隔…

O2OA开发知识-后端代理/接口脚本编写也能像前端一样用上debugger

在o2oa开发平台中&#xff0c;后端代理或者接口的脚本编写也能像前端一样用上debugger&#xff0c;这是来自藕粉社区用户的宝贵技术支持。 感谢藕粉社区论坛用户提供的技术分享&#xff01;tzengsh_BTstthttps://www.o2oa.net/forum/space-uid-4410.html 论坛地址&#xff1a…

【Kubernetes】k8s集群图形化管理工具之rancher

目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实…

2024年高教社杯数学建模国赛A题思路解析+代码+论文

2024年高教社杯全国大学生数学建模竞赛&#xff08;以下简称国赛&#xff09;将于9月5日晚6时正式开始。 下文包含&#xff1a;2024国赛思路解析​、国赛参赛时间及规则信息说明、好用的数模技巧及如何备战数学建模竞赛 C君将会第一时间发布选题建议、所有题目的思路解析、相…

Axure:引领智慧时代的数据可视化原型设计先锋

在数字化转型的浪潮中&#xff0c;智慧农业、智慧城市、智慧社区、智慧水务等概念如雨后春笋般涌现&#xff0c;它们不仅重塑了我们的生活空间&#xff0c;也对数据可视化提出了前所未有的要求。作为原型设计领域的佼佼者&#xff0c;Axure RP凭借其强大的交互设计能力和直观的…

关于Nachi机器人自动运行上电条件

Nachi 机器人有两种控制柜&#xff0c;分别为 FD 控制柜和 CFD 控制柜。 对于 FD 控制器&#xff0c;执行以下操作。 1.旋转控制柜钥匙&#xff0c;使其对准标注位置①。 2.旋转示教器旋钮至下图所示位置。然后依次单击绿色按钮与白色按钮&#xff0c;机器人上电运行。 对于…